├── .idea ├── appium.iml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── Base ├── BaseAdb.py ├── BaseAndroidPhone.py ├── BaseApk.py ├── BaseAppiumServer.py ├── BaseConfig.py ├── BaseElementEnmu.py ├── BaseEmail.py ├── BaseError.py ├── BaseExcel.py ├── BaseFile.py ├── BaseInit.py ├── BaseIosCommand.py ├── BaseLog.py ├── BaseOperate.py ├── BasePickle.py ├── BaseRunner.py ├── BaseStatistics.py ├── BaseWebServer.py ├── BaseYaml.py ├── HTMLTestRunner.py ├── __init__.py └── __pycache__ │ ├── BaseAdb.cpython-34.pyc │ ├── BaseAndroidPhone.cpython-34.pyc │ ├── BaseApk.cpython-34.pyc │ ├── BaseAppiumServer.cpython-34.pyc │ ├── BaseConfig.cpython-34.pyc │ ├── BaseElementEnmu.cpython-34.pyc │ ├── BaseError.cpython-34.pyc │ ├── BaseExcel.cpython-34.pyc │ ├── BaseFile.cpython-34.pyc │ ├── BaseInit.cpython-34.pyc │ ├── BaseLog.cpython-34.pyc │ ├── BaseOperate.cpython-34.pyc │ ├── BasePickle.cpython-34.pyc │ ├── BaseRunner.cpython-34.pyc │ ├── BaseStatistics.cpython-34.pyc │ ├── BaseYaml.cpython-34.pyc │ └── __init__.cpython-34.pyc ├── CHANGELOG.md ├── Img ├── console.jpg ├── detail.jpg └── sum.png ├── Log └── sum.pickle ├── PageObject ├── Home │ ├── CardsSortPage.py │ ├── CollectSwipeDellPage.py │ ├── FirstOpenPage.py │ ├── HistorySwipeDellPage.py │ ├── __init__.py │ └── __pycache__ │ │ ├── FirstOpenPage.cpython-34.pyc │ │ └── __init__.cpython-34.pyc ├── Pages.py ├── SumResult.py ├── __init__.py └── __pycache__ │ ├── Pages.cpython-34.pyc │ ├── SumResult.cpython-34.pyc │ └── __init__.cpython-34.pyc ├── README.md ├── Report └── Report.xlsx ├── Runner ├── __init__.py ├── runner.py └── runner_ios.py ├── TestCase ├── HomeTest.py ├── __init__.py └── __pycache__ │ ├── HomeTest.cpython-34.pyc │ └── __init__.cpython-34.pyc ├── app ├── android-system-webview-60.apk ├── appium-uiautomator2-server-debug-androidTest.apk ├── appium-uiautomator2-server-v0.1.9.apk └── kill5037.bat ├── doc ├── Appium_Android.docx ├── Appium_IOS.docx ├── ~$pium_Android.docx └── 自动化框架使用说明.docx ├── mark.md ├── use.md └── yamls └── home └── firstOpen.yaml /.idea/appium.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 95 | 96 | -------runnerCaseApp 97 | ----operate---- 98 | operate1 99 | operate 100 | cts 101 | --- 102 | devices_Pool 103 | send_keys 104 | setUpModule 105 | activi 106 | datetime 107 | -- 108 | date 109 | self.data 110 | detail 111 | read 112 | --------- Element.INFO-------- 113 | iv_write 114 | ------------ 115 | tv_publish 116 | elements_by 117 | webview 118 | runnerPool 119 | com.baiji.jianshu.account.SplashScreenActivity 120 | ------------- 121 | aat 122 | Log 123 | /mcloud/mag/ProxyForText/knowledge/app/v5/findcards/cn 124 | packagename 125 | com.ximalaya.ting.android.host.activity.WelComeActivity 126 | 127 | 128 | driver 129 | data 130 | XXX 131 | 132 | 133 | 134 | 136 | 137 | 140 | 141 | 142 | 197 | 198 | 199 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | Internationalization issues 213 | 214 | 215 | Python 216 | 217 | 218 | 219 | 220 | PyTypeCheckerInspection 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 249 | 250 | 253 | 254 | 255 | 256 | 259 | 260 | 263 | 264 | 267 | 268 | 271 | 272 | 273 | 274 | 277 | 278 | 281 | 282 | 285 | 286 | 287 | 288 | 291 | 292 | 295 | 296 | 299 | 300 | 301 | 302 | 305 | 306 | 309 | 310 | 313 | 314 | 315 | 316 | 319 | 320 | 323 | 324 | 327 | 328 | 329 | 330 | 333 | 334 | 337 | 338 | 341 | 342 | 343 | 344 | 347 | 348 | 351 | 352 | 355 | 356 | 359 | 360 | 361 | 362 | 365 | 366 | 369 | 370 | 373 | 374 | 375 | 376 | 379 | 380 | 383 | 384 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 429 | 430 | 431 | 432 | 448 | 449 | 465 | 466 | 482 | 483 | 499 | 500 | 521 | 522 | 523 | 524 | 525 | 541 | 542 | 553 | 554 | 572 | 573 | 591 | 592 | 612 | 613 | 634 | 635 | 658 | 659 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 696 | 697 | 698 | 699 | 1495681351908 700 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 782 | 783 | 785 | 786 | 787 | 789 | 790 | 791 | 792 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | No facets are configured 1058 | 1059 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1.8 1081 | 1082 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | appium 1093 | 1094 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | -------------------------------------------------------------------------------- /Base/BaseAdb.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | 4 | import subprocess 5 | 6 | import os 7 | import random 8 | 9 | 10 | class AndroidDebugBridge(object): 11 | def call_adb(self, command): 12 | command_result = '' 13 | command_text = 'adb %s' % command 14 | # print(command_text) 15 | results = os.popen(command_text, "r") 16 | while 1: 17 | line = results.readline() 18 | if not line: break 19 | command_result += line 20 | results.close() 21 | return command_result 22 | 23 | # check for any fastboot device 24 | def fastboot(self, device_id): 25 | pass 26 | 27 | # 检查设备 28 | def attached_devices(self): 29 | # result = self.call_adb("devices") 30 | devices = [] 31 | result = subprocess.Popen("adb devices", shell=True, stdout=subprocess.PIPE, 32 | stderr=subprocess.PIPE).stdout.readlines() 33 | 34 | for item in result: 35 | t = item.decode().split("\tdevice") 36 | if len(t) >= 2: 37 | devices.append(t[0]) 38 | # print(result) 39 | # print(devices) 40 | return devices 41 | # 状态 42 | def get_state(self): 43 | result = self.call_adb("get-state") 44 | result = result.strip(' \t\n\r') 45 | return result or None 46 | #重启 47 | def reboot(self, option): 48 | command = "reboot" 49 | if len(option) > 7 and option in ("bootloader", "recovery",): 50 | command = "%s %s" % (command, option.strip()) 51 | self.call_adb(command) 52 | 53 | # 将电脑文件拷贝到手机里面 54 | def push(self, local, remote): 55 | result = self.call_adb("push %s %s" % (local, remote)) 56 | return result 57 | 58 | # 拉数据到本地 59 | def pull(self, remote, local): 60 | result = self.call_adb("pull %s %s" % (remote, local)) 61 | return result 62 | # 同步更新 很少用此命名 63 | def sync(self, directory, **kwargs): 64 | command = "sync %s" % directory 65 | if 'list' in kwargs: 66 | command += " -l" 67 | result = self.call_adb(command) 68 | return result 69 | 70 | # 打开指定app 71 | def open_app(self,packagename,activity): 72 | result = self.call_adb("shell am start -n %s/%s" % (packagename, activity)) 73 | check = result.partition('\n')[2].replace('\n', '').split('\t ') 74 | if check[0].find("Error") >= 1: 75 | return False 76 | else: 77 | return True 78 | 79 | # 根据包名得到进程id 80 | def get_app_pid(self, pkg_name): 81 | string = self.call_adb("shell ps | grep "+pkg_name) 82 | # print(string) 83 | if string == '': 84 | return "the process doesn't exist." 85 | result = string.split(" ") 86 | # print(result[4]) 87 | return result[4] 88 | 89 | if __name__ == '__main__': 90 | 91 | # reuslt = AndroidDebugBridge().attached_devices() 92 | # print(reuslt) 93 | 94 | print(os.popen("adb devices", 'r').readline()) -------------------------------------------------------------------------------- /Base/BaseAndroidPhone.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | __author__ = 'shikun' 4 | import os 5 | import re 6 | import math 7 | from math import ceil 8 | import subprocess 9 | # 得到手机信息 10 | def getPhoneInfo(devices): 11 | cmd = "adb -s " + devices +" shell cat /system/build.prop " 12 | print(cmd) 13 | # phone_info = os.popen(cmd).readlines() 14 | phone_info = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.readlines() 15 | result = {"release": "5.0", "model": "model2", "brand": "brand1", "device": "device1"} 16 | release = "ro.build.version.release=" # 版本 17 | model = "ro.product.model=" #型号 18 | brand = "ro.product.brand=" # 品牌 19 | device = "ro.product.device=" # 设备名 20 | for line in phone_info: 21 | for i in line.split(): 22 | temp = i.decode() 23 | if temp.find(release) >= 0: 24 | result["release"] = temp[len(release):] 25 | break 26 | if temp.find(model) >= 0: 27 | result["model"] = temp[len(model):] 28 | break 29 | if temp.find(brand) >= 0: 30 | result["brand"] = temp[len(brand):] 31 | break 32 | if temp.find(device) >= 0: 33 | result["device"] = temp[len(device) :] 34 | break 35 | print(result) 36 | return result 37 | 38 | # 得到最大运行内存 39 | def get_men_total(devices): 40 | cmd = "adb -s "+devices+ " shell cat /proc/meminfo" 41 | get_cmd = os.popen(cmd).readlines() 42 | men_total = 0 43 | men_total_str = "MemTotal" 44 | for line in get_cmd: 45 | if line.find(men_total_str) >= 0: 46 | men_total = line[len(men_total_str) +1:].replace("kB", "").strip() 47 | break 48 | return int(men_total) 49 | # 得到几核cpu 50 | def get_cpu_kel(devices): 51 | cmd = "adb -s " +devices +" shell cat /proc/cpuinfo" 52 | get_cmd = os.popen(cmd).readlines() 53 | find_str = "processor" 54 | int_cpu = 0 55 | for line in get_cmd: 56 | if line.find(find_str) >= 0: 57 | int_cpu += 1 58 | return str(int_cpu) + "核" 59 | 60 | # 得到手机分辨率 61 | def get_app_pix(devices): 62 | result = os.popen("adb -s " + devices+ " shell wm size", "r") 63 | return result.readline().split("Physical size:")[1] 64 | 65 | if __name__=="__main__": 66 | getPhoneInfo("DU2TAN15AJ049163") 67 | -------------------------------------------------------------------------------- /Base/BaseApk.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | __author__ = 'shikun' 4 | from math import floor 5 | import subprocess 6 | import os 7 | import json 8 | ''' 9 | apk文件的读取信息 10 | ''' 11 | class ApkInfo(): 12 | def __init__(self, apkPath): 13 | self.apkPath = apkPath 14 | 15 | # 得到app的文件大小 16 | def getApkSize(self): 17 | size = floor(os.path.getsize(self.apkPath) / (1024 * 1000)) 18 | return str(size) + "M" 19 | 20 | 21 | def getApkBaseInfo(self): 22 | p = subprocess.Popen("aapt dump badging %s" % self.apkPath, stdout=subprocess.PIPE, 23 | stderr=subprocess.PIPE, 24 | stdin=subprocess.PIPE, shell=True) 25 | (output, err) = p.communicate() 26 | match = re.compile("package: name='(\S+)' versionCode='(\d+)' versionName='(\S+)'").match(output.decode()) 27 | if not match: 28 | raise Exception("can't get packageinfo") 29 | packagename = match.group(1) 30 | versionCode = match.group(2) 31 | versionName = match.group(3) 32 | 33 | print('packagename:' + packagename) 34 | print('versionCode:' + versionCode) 35 | print('versionName:' + versionName) 36 | return packagename, versionName, versionCode 37 | 38 | #得到应用名字 39 | def getApkName(self): 40 | p = subprocess.Popen("aapt dump badging %s" % self.apkPath, stdout=subprocess.PIPE, 41 | stderr=subprocess.PIPE, 42 | stdin=subprocess.PIPE, shell=True) 43 | (output, err) = p.communicate() 44 | t = output.decode().split() 45 | for item in t: 46 | # print(item) 47 | match = re.compile("application-label:(\S+)").search(item) 48 | if match is not None: 49 | return match.group(1) 50 | 51 | 52 | #得到启动类 53 | 54 | def getApkActivity(self): 55 | p = subprocess.Popen("aapt dump badging %s" % self.apkPath, stdout=subprocess.PIPE, 56 | stderr=subprocess.PIPE, 57 | stdin=subprocess.PIPE, shell=True) 58 | (output, err) = p.communicate() 59 | print("=====getApkActivity=========") 60 | match = re.compile("launchable-activity: name=(\S+)").search(output.decode()) 61 | print("match=%s" %match) 62 | if match is not None: 63 | return match.group(1) 64 | if __name__ == '__main__': 65 | pass 66 | # ApkInfo(r"D:\app\appium\Img\Jianshu-2.3.1.apk").getApkActivity() 67 | # ApkInfo(r"D:\app\appium\Img\Jianshu-2.3.1.apk").getApkActivity() 68 | # # ApkInfo(r"D:\app\appium_study\Img\t.apk").get_apk_version() 69 | # # ApkInfo(r"D:\app\appium_study\Img\t.apk").get_apk_name() 70 | # ApkInfo(r"D:\app\appium_study\img\t.apk").get_apk_activity() 71 | # ApkInfo(r"D:\app\appium_study\Img\t.apk").get_apk_activity() 72 | 73 | 74 | -------------------------------------------------------------------------------- /Base/BaseAppiumServer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import socket 4 | import urllib.request 5 | from urllib.error import URLError 6 | from multiprocessing import Process 7 | import time 8 | import platform 9 | import subprocess 10 | 11 | PATH = lambda p: os.path.abspath( 12 | os.path.join(os.path.dirname(__file__), p) 13 | ) 14 | import threading 15 | 16 | 17 | class AppiumServer: 18 | def __init__(self, kwargs=None): 19 | self.kwargs = kwargs 20 | 21 | def start_server(self): 22 | """start the appium server 23 | """ 24 | for i in range(0, len(self.kwargs)): 25 | cmd = "appium --session-override -p %s -bp %s -U %s" % ( 26 | self.kwargs[i]["port"], self.kwargs[i]["bport"], self.kwargs[i]["devices"]) 27 | print(cmd) 28 | if platform.system() == "Windows": # windows下启动server 29 | t1 = RunServer(cmd) 30 | p = Process(target=t1.start()) 31 | p.start() 32 | while True: 33 | print("--------start_win_server-------------") 34 | if self.win_is_runnnig("http://127.0.0.1:" + self.kwargs[i]["port"] + "/wd/hub" + "/status"): 35 | print("-------win_server_ 成功--------------") 36 | break 37 | else: 38 | appium = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, 39 | close_fds=True) 40 | while True: 41 | appium_line = appium.stdout.readline().strip().decode() 42 | time.sleep(1) 43 | print("---------start_server----------") 44 | if 'listener started' in appium_line or 'Error: listen' in appium_line: 45 | print("----server_ 成功---") 46 | break 47 | 48 | def win_is_runnnig(self, url): 49 | """Determine whether server is running 50 | :return:True or False 51 | """ 52 | response = None 53 | time.sleep(1) 54 | try: 55 | response = urllib.request.urlopen(url, timeout=5) 56 | 57 | if str(response.getcode()).startswith("2"): 58 | return True 59 | else: 60 | return False 61 | except URLError: 62 | return False 63 | except socket.timeout: 64 | return False 65 | finally: 66 | if response: 67 | response.close() 68 | 69 | def stop_server(self, devices): 70 | sysstr = platform.system() 71 | 72 | if sysstr == 'Windows': 73 | os.popen("taskkill /f /im node.exe") 74 | else: 75 | for device in devices: 76 | # mac 77 | cmd = "lsof -i :{0}".format(device["port"]) 78 | plist = os.popen(cmd).readlines() 79 | plisttmp = plist[1].split(" ") 80 | plists = plisttmp[1].split(" ") 81 | # print plists[0] 82 | os.popen("kill -9 {0}".format(plists[0])) 83 | 84 | def re_start_server(self): 85 | """reStart the appium server 86 | """ 87 | # self.stop_server() 88 | # self.start_server() 89 | pass 90 | 91 | 92 | class RunServer(threading.Thread): 93 | def __init__(self, cmd): 94 | threading.Thread.__init__(self) 95 | self.cmd = cmd 96 | 97 | def run(self): 98 | os.system(self.cmd) 99 | 100 | 101 | if __name__ == "__main__": 102 | 103 | pass -------------------------------------------------------------------------------- /Base/BaseConfig.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | # requests.packages.urllib.disable_warnings(InsecureRequestWarning) 4 | from urllib3 import disable_warnings 5 | from urllib3.connectionpool import InsecureRequestWarning 6 | disable_warnings(InsecureRequestWarning) 7 | 8 | 9 | import json 10 | 11 | # 封装HTTP GET请求方法 12 | def get(**kwargs): 13 | data = {} 14 | url = kwargs["protocol"] + "://"+kwargs["host"]+ kwargs["url"] 15 | print(url) 16 | r = requests.get(url, headers=kwargs.get("headers", None), verify=False) 17 | if r.status_code == 200 and len(r.text) > 0: 18 | r.encoding = 'UTF-8' 19 | data = json.loads(r.text) 20 | data["status_code"] = r.status_code 21 | # print(data) 22 | return data 23 | # 封装HTTP POST请求方法,支持上传图片 24 | def post(files=None, **kwargs): 25 | result = {} 26 | # url = kwargs["protocol"] + "://" + kwargs["host"] + ':' + str(kwargs["port"])+ kwargs["url"] 27 | url = kwargs["protocol"] + "://" + kwargs["host"] + kwargs["url"] 28 | data = None 29 | if kwargs.get("data", "none") != "none": 30 | data = json.dumps(kwargs["data"]) 31 | print(data) 32 | print(url) 33 | r = requests.post(url, files=files, data=data, verify=False, headers=kwargs["headers"]) 34 | result["status_code"] = r.status_code 35 | if r.status_code == 200 and len(r.text) > 0: 36 | r.encoding = 'UTF-8' 37 | result = json.loads(r.text) 38 | result["status_code"] = r.status_code 39 | return result 40 | ''' 41 | 登陆 42 | ''' 43 | def post_login(**kwargs): 44 | result = {} 45 | # url = kwargs["protocol"] + "://" + kwargs["host"] + ':' + str(kwargs["port"])+ kwargs["url"] 46 | url = kwargs["protocol"] + "://" + kwargs["host"] + kwargs["url"] 47 | data = None 48 | if kwargs.get("data", "none") != "none": 49 | data = json.dumps(kwargs["data"]) 50 | print(data) 51 | print(url) 52 | r = requests.post(url, data=data, verify=False, headers=kwargs["headers"]) 53 | if len(r.text): 54 | r.encoding = 'UTF-8' 55 | result = json.loads(r.text) 56 | result["status_code"] = r.status_code 57 | if result["status_code"] == 200: 58 | result["cookie"] = r.headers.get("Set-Cookie") 59 | # print("--登陆接口--") 60 | # print(result) 61 | return result 62 | if __name__ == '__main__': 63 | 64 | 65 | headers = {'content-type': 'application/json'} 66 | post(protocol="http", host="ivt3.hschefu.com", port=9199, url="/login", data={'password': '12345678','username': 'xiangjin'}, headers=headers) -------------------------------------------------------------------------------- /Base/BaseElementEnmu.py: -------------------------------------------------------------------------------- 1 | 2 | class Element(object): 3 | 4 | # 常用操作关键字 5 | find_element_by_id = "id" 6 | find_elements_by_id = "ids" 7 | INDEX = "index" 8 | find_elements_by_xpath = "xpaths" 9 | find_element_by_xpath = "xpath" 10 | find_element_by_css_selector = "css" 11 | find_element_by_class_name = "class_name" 12 | CLICK = "click" 13 | TAP = "tap" 14 | ACCESSIBILITY = "accessibility" 15 | ADB_TAP = "adb_tap" 16 | SWIPE_DOWN = "swipe_down" 17 | SWIPE_UP = "swipe_up" 18 | SWIPE_LEFT = "swipe_left" 19 | SET_VALUE = "set_value" 20 | GET_VALUE = "get_value" 21 | WAIT_TIME = 20 22 | PRESS_KEY_CODE = "press_keycode" 23 | 24 | GET_CONTENT_DESC = "get_content_desc" 25 | 26 | # 错误日志 27 | TIME_OUT = "timeout" 28 | NO_SUCH = "noSuch" 29 | WEB_DROVER_EXCEPTION = "WebDriverException" 30 | INDEX_ERROR = "index_error" 31 | STALE_ELEMENT_REFERENCE_EXCEPTION = "StaleElementReferenceException" 32 | DEFAULT_ERROR = "default_error" 33 | 34 | # 检查点 35 | CONTRARY = "contrary" # 相反检查点,表示如果检查元素存在就说明失败,如删除后,此元素依然存在 36 | CONTRARY_GETVAL = "contrary_getval" # 检查点关键字contrary_getval: 相反值检查点,如果对比成功,说明失败 37 | DEFAULT_CHECK = "default_check" # 默认检查点,就是查找页面元素 38 | COMPARE = "compare" # 历史数据和实际数据对比 39 | TOAST = "toast" 40 | 41 | 42 | RE_CONNECT = 1 # 是否打开失败后再次运行一次用例 43 | 44 | INFO_FILE = "info.pickle" 45 | SUM_FILE = "sum.pickle" 46 | DEVICES_FILE = "devices.pickle" 47 | REPORT_FILE = "Report.xlsx" 48 | -------------------------------------------------------------------------------- /Base/BaseEmail.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from email.header import Header 3 | from email.mime.text import MIMEText 4 | from email.utils import parseaddr, formataddr 5 | from email.mime.multipart import MIMEMultipart 6 | from email.mime.application import MIMEApplication 7 | import smtplib 8 | import os 9 | PATH = lambda p: os.path.abspath( 10 | os.path.join(os.path.dirname(__file__), p) 11 | ) 12 | def _format_addr(s): 13 | name, addr = parseaddr(s) 14 | return formataddr((Header(name, 'utf-8').encode(), addr)) 15 | def send_mail(**kwargs): 16 | ''' 17 | :param f: 附件路径 18 | :param to_addr:发给的人 [] 19 | :return: 20 | ''' 21 | from_addr = kwargs["mail_user"] 22 | password = kwargs["mail_pass"] 23 | # to_addr = "ashikun@126.com" 24 | smtp_server = kwargs["mail_host"] 25 | 26 | msg = MIMEMultipart() 27 | 28 | # msg = MIMEText('hello, send by Python...', 'plain', 'utf-8') 29 | msg['From'] = _format_addr('来自<%s>接口测试' % from_addr) 30 | msg['To'] = _format_addr(' <%s>' % kwargs["to_addr"]) 31 | msg['Subject'] = Header(kwargs["header_msg"], 'utf-8').encode() 32 | msg.attach(MIMEText(kwargs["attach"], 'plain', 'utf-8')) 33 | 34 | if kwargs.get("report", "0") != "0": 35 | part = MIMEApplication(open(kwargs["report"], 'rb').read()) 36 | part.add_header('Content-Disposition', 'attachment', filename=('gb2312', '', kwargs["report_name"])) 37 | msg.attach(part) 38 | 39 | server = smtplib.SMTP_SSL(smtp_server, kwargs["port"]) 40 | server.set_debuglevel(1) 41 | server.login(from_addr, password) 42 | server.sendmail(from_addr, kwargs["to_addr"], msg.as_string()) 43 | server.quit() 44 | if __name__ == '__main__': 45 | to_addr = ["284772894@qq.com"] 46 | mail_host = "smtp.qq.com" 47 | mail_user = "284772894@qq.com" 48 | mail_pass = "oftllbhnknegbjhb" 49 | port = "465" 50 | header_msg = "接口测试" 51 | attach = "接口测试" 52 | report = PATH("../Runner/report.xlsx") 53 | send_mail(to_addr = to_addr, mail_host = mail_host, mail_user=mail_user, port=port, mail_pass=mail_pass, header_msg=header_msg, report=report, attach=attach, report_name="接口测试报告") 54 | -------------------------------------------------------------------------------- /Base/BaseError.py: -------------------------------------------------------------------------------- 1 | from Base.BaseElementEnmu import Element 2 | 3 | """ 4 | element_info: 元素 5 | info: 用例说明 6 | current: 当前值 7 | history: 历史值 8 | type: 错误类型 9 | """ 10 | 11 | 12 | def get_error(kw): 13 | elements = { 14 | Element.TIME_OUT: lambda: "==%s请求超时==" % kw["element_info"], 15 | Element.NO_SUCH: lambda: "==%s不存在==" % kw["element_info"], 16 | Element.WEB_DROVER_EXCEPTION: lambda: "==%s的driver错误==" % kw["element_info"], 17 | Element.INDEX_ERROR: lambda: "==%s索引错误==" % kw["element_info"], 18 | Element.STALE_ELEMENT_REFERENCE_EXCEPTION: lambda: "==%s页面元素已经发生==" % kw["element_info"], 19 | Element.DEFAULT_ERROR: lambda: "==请检查%s==" % kw["element_info"], 20 | Element.CONTRARY: lambda: "==检查点_%s失败_%s依然在页面==" % (kw["info"], kw["element_info"]), 21 | Element.CONTRARY_GETVAL: lambda: "==检查点_对比数据失败,当前取到到数据为:%s,历史取到数据为:%s" % (kw["current"], kw["history"]), 22 | Element.DEFAULT_CHECK: lambda: "==检查点_%s失败,请检查_%s==" % (kw["info"], kw["element_info"]), 23 | Element.COMPARE: lambda: "==检查点_对比数据失败,当前取到到数据为:%s,历史取到数据为:%s" % (kw["current"], kw["history"]), 24 | Element.TOAST: lambda: "==检查点_%s_查找弹框失败==" % kw["element_info"] 25 | } 26 | return elements[kw["type"]]() 27 | -------------------------------------------------------------------------------- /Base/BaseExcel.py: -------------------------------------------------------------------------------- 1 | __author__ = 'shikun' 2 | import xlsxwriter 3 | import os 4 | 5 | PATH = lambda p: os.path.abspath( 6 | os.path.join(os.path.dirname(__file__), p) 7 | ) 8 | 9 | 10 | class OperateReport: 11 | def __init__(self, wd): 12 | self.wd = wd 13 | 14 | def init(self, worksheet, data, devices): 15 | # 设置列行的宽高 16 | worksheet.set_column("A:A", 15) 17 | worksheet.set_column("B:B", 20) 18 | worksheet.set_column("C:C", 20) 19 | worksheet.set_column("D:D", 20) 20 | worksheet.set_column("E:E", 20) 21 | 22 | worksheet.set_row(1, 30) 23 | worksheet.set_row(2, 30) 24 | worksheet.set_row(3, 30) 25 | worksheet.set_row(4, 30) 26 | worksheet.set_row(5, 30) 27 | worksheet.set_row(6, 30) 28 | worksheet.set_row(7, 30) 29 | worksheet.set_row(8, 30) 30 | 31 | define_format_H1 = get_format(self.wd, {'bold': True, 'font_size': 18}) 32 | define_format_H2 = get_format(self.wd, {'bold': True, 'font_size': 14}) 33 | define_format_H1.set_border(1) 34 | 35 | define_format_H2.set_border(1) 36 | define_format_H1.set_align("center") 37 | define_format_H2.set_align("center") 38 | define_format_H2.set_bg_color("blue") 39 | define_format_H2.set_color("#ffffff") 40 | 41 | worksheet.merge_range('A1:E1', '测试报告总概况', define_format_H1) 42 | worksheet.merge_range('A2:E2', 'WebLink知识测试概括', define_format_H2) 43 | 44 | _write_center(worksheet, "A3", 'versionCode', self.wd) 45 | _write_center(worksheet, "A4", 'versionName', self.wd) 46 | _write_center(worksheet, "A5", 'packingTime', self.wd) 47 | _write_center(worksheet, "A6", '测试日期', self.wd) 48 | 49 | _write_center(worksheet, "B3", data['versionCode'], self.wd) 50 | _write_center(worksheet, "B4", data['versionName'], self.wd) 51 | _write_center(worksheet, "B5", data['packingTime'], self.wd) 52 | _write_center(worksheet, "B6", data['testDate'], self.wd) 53 | 54 | _write_center(worksheet, "C3", "用例总数", self.wd) 55 | _write_center(worksheet, "C4", "通过总数", self.wd) 56 | _write_center(worksheet, "C5", "失败总数", self.wd) 57 | _write_center(worksheet, "C6", "测试耗时", self.wd) 58 | 59 | # data1 = {"test_sum": 100, "test_success": 80, "test_failed": 20, "test_date": "2018-10-10 12:10"} 60 | _write_center(worksheet, "D3", data['sum'], self.wd) 61 | _write_center(worksheet, "D4", data['pass'], self.wd) 62 | _write_center(worksheet, "D5", data['fail'], self.wd) 63 | _write_center(worksheet, "D6", data['testSumDate'], self.wd) 64 | 65 | _write_center(worksheet, "E3", "脚本语言", self.wd) 66 | 67 | worksheet.merge_range('E4:E6', 'appium1.7+python3', get_format_center(self.wd)) 68 | _write_center(worksheet, "A8", '机型', self.wd) 69 | _write_center(worksheet, "B8", '通过', self.wd) 70 | _write_center(worksheet, "C8", '失败', self.wd) 71 | 72 | temp = 9 73 | for item in devices: 74 | _write_center(worksheet, "A%s" % temp, item["phone_name"], self.wd) 75 | _write_center(worksheet, "B%s" % temp, item["pass"], self.wd) 76 | _write_center(worksheet, "C%s" % temp, item["fail"], self.wd) 77 | temp = temp + 1 78 | 79 | pie(self.wd, worksheet) 80 | 81 | def detail(self, worksheet, info): 82 | # 设置列行的宽高 83 | worksheet.set_column("A:A", 30) 84 | worksheet.set_column("B:B", 20) 85 | worksheet.set_column("C:C", 20) 86 | worksheet.set_column("D:D", 20) 87 | worksheet.set_column("E:E", 20) 88 | worksheet.set_column("F:F", 20) 89 | worksheet.set_column("G:G", 20) 90 | worksheet.set_column("H:H", 20) 91 | worksheet.set_column("I:I", 20) 92 | worksheet.set_column("J:J", 20) 93 | 94 | worksheet.set_row(1, 30) 95 | worksheet.set_row(2, 30) 96 | worksheet.set_row(3, 30) 97 | worksheet.set_row(4, 30) 98 | worksheet.set_row(5, 30) 99 | worksheet.set_row(6, 30) 100 | worksheet.set_row(7, 30) 101 | worksheet.set_row(8, 30) 102 | worksheet.set_row(9, 30) 103 | worksheet.set_row(10, 30) 104 | 105 | worksheet.merge_range('A1:J1', '测试详情', get_format(self.wd, {'bold': True, 'font_size': 18, 'align': 'center', 106 | 'valign': 'vcenter', 'bg_color': 'blue', 107 | 'font_color': '#ffffff'})) 108 | _write_center(worksheet, "A2", '机型', self.wd) 109 | _write_center(worksheet, "B2", '用例ID', self.wd) 110 | _write_center(worksheet, "C2", '用例介绍', self.wd) 111 | _write_center(worksheet, "D2", '用例函数', self.wd) 112 | _write_center(worksheet, "E2", '前置条件', self.wd) 113 | _write_center(worksheet, "F2", '操作步骤 ', self.wd) 114 | _write_center(worksheet, "G2", '检查点 ', self.wd) 115 | _write_center(worksheet, "H2", '测试结果 ', self.wd) 116 | _write_center(worksheet, "I2", '备注 ', self.wd) 117 | _write_center(worksheet, "J2", '截图', self.wd) 118 | 119 | temp = 3 120 | for item in info: 121 | # print(item) 122 | _write_center(worksheet, "A" + str(temp), item["phoneName"], self.wd) 123 | _write_center(worksheet, "B" + str(temp), item["id"], self.wd) 124 | _write_center(worksheet, "C" + str(temp), item["title"], self.wd) 125 | _write_center(worksheet, "D" + str(temp), item["caseName"], self.wd) 126 | _write_center(worksheet, "E" + str(temp), item["info"], self.wd) 127 | _write_center(worksheet, "F" + str(temp), item["step"], self.wd) 128 | _write_center(worksheet, "G" + str(temp), item["checkStep"], self.wd) 129 | _write_center(worksheet, "H" + str(temp), item["result"], self.wd) 130 | _write_center(worksheet, "I" + str(temp), item.get("msg", ""), self.wd) 131 | if item.get("img", "false") == "false": 132 | _write_center(worksheet, "J" + str(temp), "", self.wd) 133 | worksheet.set_row(temp, 30) 134 | else: 135 | worksheet.insert_image('J' + str(temp), item["img"], 136 | {'x_scale': 0.1, 'y_scale': 0.1, 'border': 1}) 137 | worksheet.set_row(temp - 1, 110) 138 | temp = temp + 1 139 | 140 | def close(self): 141 | self.wd.close() 142 | 143 | 144 | def get_format(wd, option={}): 145 | return wd.add_format(option) 146 | 147 | 148 | # def link_format(wd): 149 | # red_format = wd.add_format({ 150 | # 'font_color': 'red', 151 | # 'bold': 1, 152 | # 'underline': 1, 153 | # 'font_size': 12, 154 | # }) 155 | def get_format_center(wd, num=1): 156 | return wd.add_format({'align': 'center', 'valign': 'vcenter', 'border': num}) 157 | 158 | 159 | def set_border_(wd, num=1): 160 | return wd.add_format({}).set_border(num) 161 | 162 | 163 | def _write_center(worksheet, cl, data, wd): 164 | return worksheet.write(cl, data, get_format_center(wd)) 165 | 166 | 167 | def set_row(worksheet, num, height): 168 | worksheet.set_row(num, height) 169 | 170 | # 生成饼形图 171 | 172 | 173 | def pie(workbook, worksheet): 174 | chart1 = workbook.add_chart({'type': 'pie'}) 175 | chart1.add_series({ 176 | 'name': '自动化测试统计', 177 | 'categories': '=测试总况!$C$4:$C$5', 178 | 'values': '=测试总况!$D$4:$D$5', 179 | }) 180 | chart1.set_title({'name': '测试统计'}) 181 | chart1.set_style(10) 182 | worksheet.insert_chart('A9', chart1, {'x_offset': 25, 'y_offset': 10}) 183 | 184 | 185 | if __name__ == '__main__': 186 | sum = {'testSumDate': '25秒', 'sum': 10, 'pass': 5, 'testDate': '2017-06-05 15:26:49', 'fail': 5, 187 | 'appVersion': '17051515', 'appSize': '14M', 'appName': "'简书'"} 188 | info = [{"id": 1, "title": "第一次打开", "caseName": "testf01", "result": "通过", "phoneName": "三星"}, 189 | {"id": 1, "title": "第一次打开", 190 | "caseName": "testf01", "result": "通过", "img": "d:\\1.PNG", "phoneName": "华为"}] 191 | workbook = xlsxwriter.Workbook('Report.xlsx') 192 | worksheet = workbook.add_worksheet("测试总况") 193 | worksheet2 = workbook.add_worksheet("测试详情") 194 | bc = OperateReport(wd=workbook) 195 | bc.init(worksheet, sum) 196 | bc.detail(worksheet2, info) 197 | bc.close() 198 | # 199 | -------------------------------------------------------------------------------- /Base/BaseFile.py: -------------------------------------------------------------------------------- 1 | __author__ = 'shikun' 2 | 3 | import os 4 | 5 | 6 | ''' 7 | 操作文件 8 | ''' 9 | 10 | 11 | def write_data(f, method='w+', data=""): 12 | if not os.path.isfile(f): 13 | print('文件不存在,写入数据失败') 14 | else: 15 | with open(f, method, encoding="utf-8") as fs: 16 | fs.write(data + "\n") 17 | 18 | 19 | def mkdir_file(f, method='w+'): 20 | if not os.path.isfile(f): 21 | with open(f, method, encoding="utf-8") as fs: 22 | print("创建文件%s成功" % f) 23 | pass 24 | else: 25 | print("%s文件已经存在,创建失败" % f) 26 | pass 27 | 28 | 29 | def remove_file(f): 30 | if os.path.isfile(f): 31 | os.remove(f) 32 | else: 33 | print("%s文件不存在,无法删除" % f) 34 | -------------------------------------------------------------------------------- /Base/BaseInit.py: -------------------------------------------------------------------------------- 1 | from Base.BaseElementEnmu import Element 2 | from Base.BasePickle import * 3 | from Base.BaseFile import * 4 | 5 | PATH = lambda p: os.path.abspath( 6 | os.path.join(os.path.dirname(__file__), p) 7 | ) 8 | 9 | 10 | def mk_file(): 11 | destroy() 12 | mkdir_file(PATH("../Log/"+Element.INFO_FILE)) 13 | mkdir_file(PATH("../Log/"+Element.SUM_FILE)) 14 | mkdir_file(PATH("../Log/" + Element.DEVICES_FILE)) 15 | 16 | data = read(PATH("../Log/"+Element.INFO_FILE)) 17 | # data["appName"] = apkInfo.getApkName() 18 | # data["appSize"] = apkInfo.getApkSize() 19 | # data["appVersion"] = apkInfo.getApkBaseInfo()[2] 20 | data["versionCode"] = "40" 21 | data["versionName"] = "1.4.0" 22 | data["packingTime"] = "2017/12/4 13:00" 23 | data["sum"] = 0 24 | data["pass"] = 0 25 | data["fail"] = 0 26 | write(data=data, path=PATH("../Log/"+Element.SUM_FILE)) 27 | 28 | 29 | def init(devices): 30 | # 每次都重新安装uiautomator2都两个应用 31 | pass 32 | # os.popen("adb -s %s uninstall io.appium.uiautomator2.server.test" % devices) 33 | # os.popen("adb -s %s uninstall io.appium.uiautomator2.server" % devices) 34 | # os.popen("adb -s %s install -r %s" % (devices, PATH("../app/appium-uiautomator2-server-v0.1.9.apk"))) 35 | # os.popen("adb -s %s install -r %s" % (devices, PATH("../app/appium-uiautomator2-server-debug-androidTest.apk"))) 36 | # os.popen("adb install -r "+PATH("../app/android-system-webview-60.apk")) 37 | 38 | 39 | def destroy(): 40 | remove_file(PATH("../Log/"+Element.INFO_FILE)) 41 | remove_file(PATH("../Log/"+Element.SUM_FILE)) 42 | remove_file(PATH("../Log/"+Element.DEVICES_FILE)) 43 | 44 | 45 | if __name__ == '__main__': 46 | print(destroy()) 47 | # print(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 48 | -------------------------------------------------------------------------------- /Base/BaseIosCommand.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | import os 4 | 5 | ''' 6 | 获取ios下的硬件信息 7 | ''' 8 | 9 | 10 | def get_ios_devices(): 11 | devices = [] 12 | result = subprocess.Popen("ideviceinfo -k UniqueDeviceID", shell=True, stdout=subprocess.PIPE, 13 | stderr=subprocess.PIPE).stdout.readlines() 14 | 15 | for item in result: 16 | t = item.decode().split("\n") 17 | if len(t) >= 2: 18 | devices.append(t[0]) 19 | print(devices) 20 | return devices 21 | 22 | 23 | def get_ios_version(udid): 24 | command = "ideviceinfo -u %s -k ProductVersion" % udid 25 | result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, 26 | stderr=subprocess.PIPE).stdout.readlines() 27 | for item in result: 28 | t = item.decode().split("\n") 29 | if len(t) >= 2: 30 | return t[0] 31 | 32 | 33 | def get_ios_product_name(udid): 34 | command = "ideviceinfo -u %s -k DeviceName" % udid 35 | result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, 36 | stderr=subprocess.PIPE).stdout.readlines() 37 | for item in result: 38 | t = item.decode().split("\n") 39 | if len(t) >= 2: 40 | return t[0] 41 | 42 | 43 | # 编译facebook的wda到真机 44 | def build_wda_ios(udid): 45 | os.popen( 46 | "xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination id=" + udid + " test") 47 | 48 | 49 | if __name__ == '__main__': 50 | udid = get_ios_devices()[0] 51 | print(get_ios_product_name(udid)) 52 | -------------------------------------------------------------------------------- /Base/BaseLog.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | import os 4 | from time import sleep 5 | import threading 6 | from Base.BaseAndroidPhone import getPhoneInfo 7 | 8 | PATH = lambda p: os.path.abspath( 9 | os.path.join(os.path.dirname(__file__), p) 10 | ) 11 | 12 | 13 | class Log: 14 | def __init__(self, devices): 15 | get_phone = getPhoneInfo(devices) 16 | phone_name = get_phone["brand"] + "_" + get_phone["model"] + "_" + "android" + "_" + get_phone["release"] 17 | global logger, resultPath, logPath 18 | resultPath = PATH("../log") 19 | logPath = os.path.join(resultPath, (phone_name + time.strftime('%Y%m%d%H%M%S', time.localtime()))) 20 | if not os.path.exists(logPath): 21 | os.makedirs(logPath) 22 | self.checkNo = 0 23 | self.logger = logging.getLogger() 24 | self.logger.setLevel(logging.INFO) 25 | 26 | # create handler,write log 27 | fh = logging.FileHandler(os.path.join(logPath, "outPut.log")) 28 | # Define the output format of formatter handler 29 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 30 | fh.setFormatter(formatter) 31 | 32 | self.logger.addHandler(fh) 33 | 34 | def getMyLogger(self): 35 | """get the logger 36 | :return:logger 37 | """ 38 | return self.logger 39 | 40 | def buildStartLine(self, caseNo): 41 | """build the start log 42 | :param caseNo: 43 | :return: 44 | """ 45 | startLine = "---- " + caseNo + " " + " " + \ 46 | " ----" 47 | # startLine = "---- " + caseNo + " " + "START" + " " + \ 48 | # " ----" 49 | self.logger.info(startLine) 50 | 51 | def buildEndLine(self, caseNo): 52 | """build the end log 53 | :param caseNo: 54 | :return: 55 | """ 56 | endLine = "---- " + caseNo + " " + "END" + " " + \ 57 | " ----" 58 | self.logger.info(endLine) 59 | self.checkNo = 0 60 | 61 | def writeResult(self, result): 62 | """write the case result(OK or NG) 63 | :param result: 64 | :return: 65 | """ 66 | reportPath = os.path.join(logPath, "report.txt") 67 | flogging = open(reportPath, "a") 68 | try: 69 | flogging.write(result + "\n") 70 | finally: 71 | flogging.close() 72 | pass 73 | 74 | def resultOK(self, caseNo): 75 | self.writeResult(caseNo + ": OK") 76 | 77 | def resultNG(self, caseNo, reason): 78 | self.writeResult(caseNo + ": NG--" + reason) 79 | 80 | def checkPointOK(self, driver, caseName, checkPoint): 81 | """write the case's checkPoint(OK) 82 | :param driver: 83 | :param caseName: 84 | :param checkPoint: 85 | :return: 86 | """ 87 | self.checkNo += 1 88 | 89 | self.logger.info("[CheckPoint_" + str(self.checkNo) + "]: " + checkPoint + ": OK") 90 | print("==用例_%s检查点成功==" % caseName) 91 | # take shot 默认去掉成功截图 92 | # self.screenshotOK(driver, caseName) 93 | 94 | def checkPointNG(self, driver, caseName, checkPoint): 95 | """write the case's checkPoint(NG) 96 | :param driver: 97 | :param caseName: 98 | :param checkPoint: 99 | :return: 100 | """ 101 | self.checkNo += 1 102 | 103 | self.logger.info("[CheckPoint_" + str(self.checkNo) + "]: " + checkPoint + ": NG") 104 | 105 | # take shot 106 | return self.screenshotNG(driver, caseName) 107 | 108 | def screenshotOK(self, driver, caseName): 109 | """screen shot 110 | :param driver: 111 | :param caseName: 112 | :return: 113 | """ 114 | screenshotPath = os.path.join(logPath, caseName) 115 | screenshotName = "CheckPoint_" + str(self.checkNo) + "_OK.png" 116 | 117 | # wait for animations to complete before taking screenshot 118 | sleep(1) 119 | # driver.get_screenshot_as_file(os.path.join(screenshotPath, screenshotName)) 120 | driver.get_screenshot_as_file(os.path.join(screenshotPath + screenshotName)) 121 | 122 | def screenshotNG(self, driver, caseName): 123 | """screen shot 124 | :param driver: 125 | :param caseName: 126 | :return: 127 | """ 128 | screenshotPath = os.path.join(logPath, caseName) 129 | screenshotName = "CheckPoint_" + str(self.checkNo) + "_NG.png" 130 | 131 | # wait for animations to complete before taking screenshot 132 | sleep(1) 133 | driver.get_screenshot_as_file(os.path.join(screenshotPath + screenshotName)) 134 | return os.path.join(screenshotPath + screenshotName) 135 | 136 | def screenshotERROR(self, driver, caseName): 137 | """screen shot 138 | :param driver: 139 | :param caseName: 140 | :return: 141 | """ 142 | screenshotPath = os.path.join(logPath, caseName) 143 | screenshotName = "ERROR.png" 144 | 145 | # wait for animations to complete before taking screenshot 146 | sleep(1) 147 | driver.get_screenshot_as_file(os.path.join(screenshotPath, screenshotName)) 148 | 149 | 150 | class myLog: 151 | """ 152 | This class is used to get log 153 | """ 154 | 155 | log = None 156 | mutex = threading.Lock() 157 | 158 | def __init__(self): 159 | pass 160 | 161 | @staticmethod 162 | def getLog(devices): 163 | if myLog.log is None: 164 | myLog.mutex.acquire() 165 | myLog.log = Log(devices) 166 | myLog.mutex.release() 167 | 168 | return myLog.log 169 | 170 | 171 | if __name__ == "__main__": 172 | logTest = myLog.getLog("devices") 173 | # logger = logTest.getMyLogger() 174 | logTest.buildStartLine("11111111111111111111111") -------------------------------------------------------------------------------- /Base/BaseOperate.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import os 4 | import threading 5 | 6 | import appium.common.exceptions 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.support import expected_conditions 9 | 10 | __author__ = 'shikun' 11 | # -*- coding: utf-8 -*- 12 | from selenium.webdriver.support.ui import WebDriverWait 13 | import selenium.common.exceptions 14 | from Base.BaseElementEnmu import Element as be 15 | import time 16 | import os 17 | 18 | ''' 19 | # 此脚本主要用于查找元素是否存在,操作页面元素 20 | ''' 21 | 22 | 23 | class OperateElement: 24 | def __init__(self, driver=""): 25 | self.driver = driver 26 | 27 | def findElement(self, mOperate): 28 | ''' 29 | 查找元素.mOperate,dict|list 30 | operate_type:对应的操作 31 | element_info:元素详情 32 | find_type: find类型 33 | ''' 34 | try: 35 | if type(mOperate) == list: # 多检查点 36 | for item in mOperate: 37 | if item.get("is_webview", "0") == 1: # 1表示切换到webview 38 | self.switchToWebview() 39 | elif item.get("is_webview", "0") == 2: 40 | self.switchToNative() 41 | # if item.get("element_info", "0") == "0": # 如果没有页面元素,就不检测是页面元素,可能是滑动等操作 42 | # return {"result": True} 43 | t = item["check_time"] if item.get("check_time", "0") != "0" else be.WAIT_TIME 44 | WebDriverWait(self.driver, t).until(lambda x: self.elements_by(item)) 45 | return {"result": True} 46 | if type(mOperate) == dict: # 单检查点 47 | if mOperate.get("is_webview", "0") == 1 and self.switchToWebview() is False: # 1表示切换到webview 48 | print("切换到webview失败,请确定是否在webview页面") 49 | return {"result": False, "webview": False} 50 | elif mOperate.get("is_webview", "0") == 2: 51 | self.switchToNative() 52 | if mOperate.get("element_info", "0") == "0": # 如果没有页面元素,就不检测是页面元素,可能是滑动等操作 53 | return {"result": True} 54 | t = mOperate["check_time"] if mOperate.get("check_time", 55 | "0") != "0" else be.WAIT_TIME # 如果自定义检测时间为空,就用默认的检测等待时间 56 | WebDriverWait(self.driver, t).until(lambda x: self.elements_by(mOperate)) # 操作元素是否存在 57 | return {"result": True} 58 | except selenium.common.exceptions.TimeoutException: 59 | # print("==查找元素超时==") 60 | return {"result": False, "type": be.TIME_OUT} 61 | except selenium.common.exceptions.NoSuchElementException: 62 | # print("==查找元素不存在==") 63 | return {"result": False, "type": be.NO_SUCH} 64 | except selenium.common.exceptions.WebDriverException: 65 | # print("WebDriver出现问题了") 66 | return {"result": False, "type": be.WEB_DROVER_EXCEPTION} 67 | 68 | ''' 69 | 查找元素.mOperate是字典 70 | operate_type:对应的操作 71 | element_info:元素详情 72 | find_type: find类型 73 | testInfo: 用例介绍 74 | logTest: 记录日志 75 | device: 设备名 76 | ''' 77 | 78 | def operate(self, mOperate, testInfo, logTest, device): 79 | res = self.findElement(mOperate) 80 | if res["result"]: 81 | return self.operate_by(mOperate, testInfo, logTest, device) 82 | else: 83 | return res 84 | 85 | def operate_by(self, operate, testInfo, logTest, device): 86 | try: 87 | info = operate.get("element_info", " ") + "_" + operate.get("operate_type", " ") + str(operate.get( 88 | "code", " ")) + operate.get("msg", " ") 89 | logTest.buildStartLine(testInfo[0]["id"] + "_" + testInfo[0]["title"] + "_" + info) # 记录日志 90 | print("==操作步骤:%s==" % info) 91 | 92 | if operate.get("operate_type", "0") == "0": # 如果没有此字段,说明没有相应操作,一般是检查点,直接判定为成功 93 | return {"result": True} 94 | 95 | # threading._start_new_thread(self.click_windows(device),()) 96 | elements = { 97 | be.SWIPE_DOWN: lambda: self.swipeToDown(), 98 | be.SWIPE_UP: lambda: self.swipeToUp(), 99 | be.CLICK: lambda: self.click(operate), 100 | be.GET_VALUE: lambda: self.get_value(operate), 101 | be.SET_VALUE: lambda: self.set_value(operate), 102 | be.ADB_TAP: lambda: self.adb_tap(operate, device), 103 | be.TAP: lambda: self.tap(operate), 104 | be.GET_CONTENT_DESC: lambda: self.get_content_desc(operate), 105 | be.PRESS_KEY_CODE: lambda: self.press_keycode(operate) 106 | 107 | } 108 | return elements[operate.get("operate_type")]() 109 | except IndexError: 110 | logTest.buildStartLine( 111 | testInfo[0]["id"] + "_" + testInfo[0]["title"] + "_" + operate["element_info"] + "索引错误") # 记录日志 112 | # print(operate["element_info"] + "索引错误") 113 | return {"result": False, "type": be.INDEX_ERROR} 114 | 115 | except selenium.common.exceptions.NoSuchElementException: 116 | logTest.buildStartLine( 117 | testInfo[0]["id"] + "_" + testInfo[0]["title"] + "_" + operate[ 118 | "element_info"] + "页面元素不存在或没加载完成") # 记录日志 119 | # print(operate["element_info"] + "页面元素不存在或没有加载完成") 120 | return {"result": False, "type": be.NO_SUCH} 121 | except selenium.common.exceptions.StaleElementReferenceException: 122 | logTest.buildStartLine( 123 | testInfo[0]["id"] + "_" + testInfo[0]["title"] + "_" + operate[ 124 | "element_info"] + "页面元素已经变化") # 记录日志 125 | # print(operate["element_info"] + "页面元素已经变化") 126 | return {"result": False, "type": be.STALE_ELEMENT_REFERENCE_EXCEPTION} 127 | except KeyError: 128 | # 如果key不存在,一般都是在自定义的page页面去处理了,这里直接返回为真 129 | return {"result": True} 130 | 131 | # 获取到元素到坐标点击,主要解决浮动层遮档无法触发driver.click的问题 132 | def adb_tap(self, mOperate, device): 133 | 134 | bounds = self.elements_by(mOperate).location 135 | x = str(bounds["x"]) 136 | y = str(bounds["y"]) 137 | 138 | cmd = "adb -s " + device + " shell input tap " + x + " " + y 139 | print(cmd) 140 | os.system(cmd) 141 | 142 | return {"result": True} 143 | 144 | def tap(self, operate): 145 | x1 = operate["bounds"][0][0] 146 | y1 = operate["bounds"][0][1] 147 | 148 | x2 = operate["bounds"][0][1] 149 | y2 = operate["bounds"][1][1] 150 | self.driver.tap([(x1, y1), (x2, y2)], operate.get("duration", 300)) 151 | 152 | return {"result": True} 153 | 154 | def toast(self, xpath, logTest, testInfo): 155 | logTest.buildStartLine(testInfo[0]["id"] + "_" + testInfo[0]["title"] + "_" + "查找弹窗元素_" + xpath) # 记录日志 156 | try: 157 | WebDriverWait(self.driver, 10, 0.5).until( 158 | expected_conditions.presence_of_element_located((By.XPATH, xpath))) 159 | return {"result": True} 160 | except selenium.common.exceptions.TimeoutException: 161 | return {"result": False} 162 | except selenium.common.exceptions.NoSuchElementException: 163 | return {"result": False} 164 | 165 | # 点击事件 166 | def click(self, mOperate): 167 | # print(self.driver.page_source) 168 | if mOperate["find_type"] == be.find_element_by_id or mOperate["find_type"] == be.find_element_by_xpath: 169 | self.elements_by(mOperate).click() 170 | elif mOperate.get("find_type") == be.find_elements_by_id: 171 | self.elements_by(mOperate)[mOperate["index"]].click() 172 | return {"result": True} 173 | 174 | # code 事件 175 | def press_keycode(self, mOperate): 176 | self.driver.press_keycode(mOperate.get("code", 0)) 177 | return {"result": True} 178 | 179 | def get_content_desc(self, mOperate): 180 | result = self.elements_by(mOperate).get_attribute("contentDescription") 181 | re_reulst = re.findall(r'[a-zA-Z\d+\u4e00-\u9fa5]', result) 182 | return {"result": True, "text": "".join(re_reulst)} 183 | 184 | ''' 185 | 切换native 186 | 187 | ''' 188 | 189 | def switchToNative(self): 190 | self.driver.switch_to.context("NATIVE_APP") # 切换到native 191 | 192 | ''' 193 | 切换webview 194 | ''' 195 | 196 | def switchToWebview(self): 197 | try: 198 | n = 1 199 | while n < 10: 200 | time.sleep(3) 201 | n = n + 1 202 | print(self.driver.contexts) 203 | for cons in self.driver.contexts: 204 | if cons.lower().startswith("webview"): 205 | self.driver.switch_to.context(cons) 206 | # print(self.driver.page_source) 207 | self.driver.execute_script('document.querySelectorAll("html")[0].style.display="block"') 208 | self.driver.execute_script('document.querySelectorAll("head")[0].style.display="block"') 209 | self.driver.execute_script('document.querySelectorAll("title")[0].style.display="block"') 210 | print("切换webview成功") 211 | return {"result": True} 212 | return {"result": False} 213 | except appium.common.exceptions.NoSuchContextException: 214 | print("切换webview失败") 215 | return {"result": False, "text": "appium.common.exceptions.NoSuchContextException异常"} 216 | 217 | # 左滑动 218 | def swipeLeft(self, mOperate): 219 | width = self.driver.get_window_size()["width"] 220 | height = self.driver.get_window_size()["height"] 221 | x1 = int(width * 0.75) 222 | y1 = int(height * 0.5) 223 | x2 = int(width * 0.05) 224 | self.driver(x1, y1, x2, y1, 600) 225 | 226 | # swipe start_x: 200, start_y: 200, end_x: 200, end_y: 400, duration: 2000 从200滑动到400 227 | def swipeToDown(self): 228 | height = self.driver.get_window_size()["height"] 229 | x1 = int(self.driver.get_window_size()["width"] * 0.5) 230 | y1 = int(height * 0.25) 231 | y2 = int(height * 0.75) 232 | 233 | self.driver.swipe(x1, y1, x1, y2, 1000) 234 | # self.driver.swipe(0, 1327, 500, 900, 1000) 235 | print("--swipeToDown--") 236 | return {"result": True} 237 | 238 | def swipeToUp(self): 239 | height = self.driver.get_window_size()["height"] 240 | width = self.driver.get_window_size()["width"] 241 | self.driver.swipe(width / 2, height * 3 / 4, width / 2, height / 4) 242 | print("执行上拉") 243 | return {"result": True} 244 | # for i in range(n): 245 | # self.driver.swipe(540, 800, 540, 560, 0) 246 | # time.sleep(2) 247 | 248 | def swipeToRight(self): 249 | height = self.driver.get_window_size()["height"] 250 | width = self.driver.get_window_size()["width"] 251 | x1 = int(width * 0.05) 252 | y1 = int(height * 0.5) 253 | x2 = int(width * 0.75) 254 | self.driver.swipe(x1, y1, x1, x2, 1000) 255 | # self.driver.swipe(0, 1327, 500, 900, 1000) 256 | print("--swipeToUp--") 257 | 258 | def set_value(self, mOperate): 259 | """ 260 | 输入值,代替过时的send_keys 261 | :param mOperate: 262 | :return: 263 | """ 264 | self.elements_by(mOperate).send_keys(mOperate["msg"]) 265 | return {"result": True} 266 | 267 | def get_value(self, mOperate): 268 | ''' 269 | 读取element的值,支持webview下获取值 270 | :param mOperate: 271 | :return: 272 | ''' 273 | 274 | if mOperate.get("find_type") == be.find_elements_by_id: 275 | element_info = self.elements_by(mOperate)[mOperate["index"]] 276 | if mOperate.get("is_webview", "0") == 1: 277 | result = element_info.text 278 | else: 279 | result = element_info.get_attribute("text") 280 | re_reulst = re.findall(r'[a-zA-Z\d+\u4e00-\u9fa5]', result) # 只匹配中文,大小写,字母 281 | return {"result": True, "text": "".join(re_reulst)} 282 | 283 | element_info = self.elements_by(mOperate) 284 | if mOperate.get("is_webview", "0") == 1: 285 | result = element_info.text 286 | else: 287 | result = element_info.get_attribute("text") 288 | 289 | re_reulst = re.findall(r'[a-zA-Z\d+\u4e00-\u9fa5]', result) 290 | return {"result": True, "text": "".join(re_reulst)} 291 | 292 | def click_windows(self, device): 293 | try: 294 | button0 = 'com.huawei.systemmanager:id/btn_allow' 295 | # button1 = 'com.android.packageinstaller:id/btn_allow_once' 296 | # button2 = 'com.android.packageinstaller:id/bottom_button_two' 297 | # button3 = 'com.android.packageinstaller:id/btn_continue_install' 298 | # button4 = 'android:id/button1' 299 | # button5 = 'vivo:id/vivo_adb_install_ok_button' 300 | button_list = [button0] 301 | for elem in button_list: 302 | find = self.driver.find_element_by_id(elem) 303 | WebDriverWait(self.driver, 1).until(lambda x: self.elements_by(find(elem))) 304 | bounds = find.location 305 | x = str(bounds["x"]) 306 | y = str(bounds["y"]) 307 | cmd = "adb -s " + device + " shell input tap " + x + " " + y 308 | print(cmd) 309 | os.system(cmd) 310 | print("==点击授权弹框_%s==" % elem) 311 | except selenium.common.exceptions.TimeoutException: 312 | # print("==查找元素超时==") 313 | pass 314 | except selenium.common.exceptions.NoSuchElementException: 315 | # print("==查找元素不存在==") 316 | pass 317 | except selenium.common.exceptions.WebDriverException: 318 | # print("WebDriver出现问题了") 319 | pass 320 | 321 | # 封装常用的标签 322 | def elements_by(self, mOperate): 323 | 324 | elements = { 325 | be.find_element_by_id: lambda: self.driver.find_element_by_id(mOperate["element_info"]), 326 | be.find_element_by_xpath: lambda: self.driver.find_element_by_xpath(mOperate["element_info"]), 327 | be.find_element_by_css_selector: lambda: self.driver.find_element_by_css_selector(mOperate['element_info']), 328 | be.find_element_by_class_name: lambda: self.driver.find_element_by_class_name(mOperate['element_info']), 329 | be.find_elements_by_id: lambda: self.driver.find_elements_by_id(mOperate['element_info']) 330 | 331 | } 332 | return elements[mOperate["find_type"]]() 333 | -------------------------------------------------------------------------------- /Base/BasePickle.py: -------------------------------------------------------------------------------- 1 | __author__ = "shikun" 2 | import pickle 3 | import os 4 | 5 | def write(data, path="data.pickle"): 6 | with open(path, 'wb') as f: 7 | pickle.dump(data, f, 0) 8 | def read(path): 9 | with open(path, 'rb') as f: 10 | try: 11 | data = pickle.load(f) 12 | except EOFError: 13 | data = {} 14 | # print("读取文件错误") 15 | # print("------read-------") 16 | # print(data) 17 | return data 18 | 19 | def readInfo(path): 20 | # data = [] 21 | with open(path, 'rb') as f: 22 | try: 23 | data = pickle.load(f) 24 | print(data) 25 | except EOFError: 26 | data = [] 27 | # print("读取文件错误") 28 | # print("------read-------") 29 | # print(data) 30 | return data 31 | 32 | 33 | 34 | def writeInfo(data="", path="data.pickle"): 35 | """ 36 | 37 | :type data: dict 38 | """ 39 | _read = readInfo(path) 40 | result = [] 41 | if _read: 42 | _read.append(data) 43 | result = _read 44 | else: 45 | result.append(data) 46 | with open(path, 'wb') as f: 47 | # print("------writeInfo-------") 48 | # print(result) 49 | pickle.dump(result, f) 50 | 51 | if __name__ == "__main__": 52 | # write("用例失败重连过一次,失败原因:", "../Log/connect64dd15b8-ca91-11e7-87ae-38c98647adce.pickle") 53 | pass -------------------------------------------------------------------------------- /Base/BaseRunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from Base.BaseAppiumServer import AppiumServer 3 | from Base.BaseLog import myLog 4 | import unittest 5 | from appium import webdriver 6 | import os 7 | from Base.BaseElementEnmu import Element 8 | import platform 9 | import time 10 | from Base.BaseYaml import getYam 11 | 12 | PATH = lambda p: os.path.abspath( 13 | os.path.join(os.path.dirname(__file__), p) 14 | ) 15 | 16 | 17 | def appium_testcase(devices): 18 | desired_caps = {} 19 | 20 | if str(devices["platformName"]).lower() == "android": 21 | # desired_caps['appPackage'] = devices["appPackage"] 22 | # desired_caps['appActivity'] = devices["appActivity"] 23 | desired_caps['udid'] = devices["deviceName"] 24 | desired_caps['app'] = devices["app"] 25 | # desired_caps["recreateChromeDriverSessions"] = "True" 26 | # 解决多次切换到webview报错问题,每次切换到非chrome-Driver时kill掉session 注意这个设置在appium 1.5版本上才做了处理 27 | else: 28 | # desired_caps['automationName'] = devices["automationName"] # Xcode8.2以上无UIAutomation,需使用XCUITest 29 | desired_caps['bundleId'] = devices["bundleId"] 30 | desired_caps['udid'] = devices["udid"] 31 | # desired_caps['newCommandTimeout'] = 3600 # 1 hour 32 | 33 | desired_caps['platformVersion'] = devices["platformVersion"] 34 | desired_caps['platformName'] = devices["platformName"] 35 | desired_caps["automationName"] = devices['automationName'] 36 | desired_caps['deviceName'] = devices["deviceName"] 37 | desired_caps["noReset"] = "True" 38 | desired_caps['noSign'] = "True" 39 | desired_caps["unicodeKeyboard"] = "True" 40 | desired_caps["resetKeyboard"] = "True" 41 | desired_caps["systemPort"] = devices["systemPort"] 42 | 43 | # desired_caps['app'] = devices["app"] 44 | remote = "http://127.0.0.1:" + str(devices["port"]) + "/wd/hub" 45 | # remote = "http://127.0.0.1:" + "4723" + "/wd/hub" 46 | driver = webdriver.Remote(remote, desired_caps) 47 | return driver 48 | 49 | 50 | class ParametrizedTestCase(unittest.TestCase): 51 | """ TestCase classes that want to be parametrized should 52 | inherit from this class. 53 | """ 54 | 55 | def __init__(self, methodName='runTest', param=None): 56 | super(ParametrizedTestCase, self).__init__(methodName) 57 | global devicess 58 | devicess = param 59 | 60 | @classmethod 61 | def setUpClass(cls): 62 | pass 63 | cls.driver = appium_testcase(devicess) 64 | cls.devicesName = devicess["deviceName"] 65 | cls.logTest = myLog().getLog(cls.devicesName) # 每个设备实例化一个日志记录器 66 | 67 | def setUp(self): 68 | pass 69 | 70 | @classmethod 71 | def tearDownClass(cls): 72 | cls.driver.close_app() 73 | cls.driver.quit() 74 | pass 75 | def tearDown(self): 76 | pass 77 | 78 | @staticmethod 79 | def parametrize(testcase_klass, param=None): 80 | # print("---parametrize-----") 81 | # print(param) 82 | testloader = unittest.TestLoader() 83 | testnames = testloader.getTestCaseNames(testcase_klass) 84 | suite = unittest.TestSuite() 85 | for name in testnames: 86 | suite.addTest(testcase_klass(name, param=param)) 87 | return suite 88 | -------------------------------------------------------------------------------- /Base/BaseStatistics.py: -------------------------------------------------------------------------------- 1 | import xlsxwriter 2 | 3 | from Base.BaseAndroidPhone import getPhoneInfo 4 | from Base.BaseElementEnmu import Element 5 | from Base.BaseExcel import OperateReport 6 | from Base.BaseInit import destroy 7 | from Base.BasePickle import * 8 | from datetime import datetime 9 | 10 | PATH = lambda p: os.path.abspath( 11 | os.path.join(os.path.dirname(__file__), p) 12 | ) 13 | 14 | ''' 15 | 统计数据相关 16 | ''' 17 | 18 | ''' 19 | result bool 20 | logTest 记录日志类 class 21 | driver 22 | testinfo 23 | 24 | ''' 25 | 26 | 27 | def countInfo(**kwargs): 28 | # get_phone = getPhoneInfo(kwargs["devices"]) 29 | # phone_name = get_phone["brand"] + "_" + get_phone["model"] + "_" + "android" + "_" + get_phone["release"] 30 | _info = {} 31 | step = "" # 操作步骤信息 32 | check_step = "" # 检查点步骤信息 33 | 34 | for case in kwargs["testCase"]: 35 | step = step + case["info"] + "\n" 36 | 37 | if type(kwargs["testCheck"]) == list: # 检查点为列表 38 | for check in kwargs["testCheck"]: 39 | check_step = check_step + check["info"] + "\n" 40 | elif type(kwargs["testCheck"]) == dict: 41 | check_step = kwargs["testCheck"]["info"] 42 | else: 43 | print("获取检查点步骤数据错误,请检查") 44 | print(kwargs["testCheck"]) 45 | 46 | _info["step"] = step # 用例操作步骤 47 | _info["checkStep"] = check_step # 用例检查点 48 | 49 | if kwargs["result"]: 50 | _info["result"] = "通过" 51 | kwargs["logTest"].checkPointOK(driver=kwargs["driver"], caseName=kwargs["testInfo"][0]["title"], 52 | checkPoint=kwargs["caseName"] + "_" + kwargs["testInfo"][0].get( 53 | "msg", " ")) 54 | else: 55 | _info["result"] = "失败" # 用例接开关 56 | _info["img"] = kwargs["logTest"].checkPointNG(driver=kwargs["driver"], caseName=kwargs["testInfo"][0]["title"], 57 | checkPoint=kwargs["caseName"] + "_" + kwargs["testInfo"][0].get( 58 | "msg", " ")) 59 | _info["id"] = kwargs["testInfo"][0]["id"] # 用例id 60 | _info["title"] = kwargs["testInfo"][0]["title"] # 用例名称 61 | _info["caseName"] = kwargs["caseName"] # 测试函数 62 | _info["phoneName"] = kwargs["phoneName"] # 手机名 63 | _info["msg"] = kwargs["testInfo"][0].get("msg", "") # 备注 64 | _info["info"] = kwargs["testInfo"][0]["info"] # 前置条件 65 | 66 | writeInfo(data=_info, path=PATH("../Log/" + Element.INFO_FILE)) 67 | # print(read(PATH("../Log/info.pickle"))) 68 | 69 | 70 | # 本地没有设备用例的记录统计 71 | def countSumNoDevices(devices, result, _read, phone_name): 72 | 73 | if _read is None: 74 | _read = [] 75 | # get_phone = getPhoneInfo(devices) 76 | # phone_name = get_phone["brand"] + "_" + get_phone["model"] + "_" + "android" + "_" + get_phone["release"] 77 | app = {"phone_name": phone_name, "pass": 0, "fail": 0, "device": devices} 78 | if result: 79 | app["pass"] = 1 80 | else: 81 | app["fail"] = 1 82 | _read.append(app) 83 | write(data=_read, path=PATH("../Log/" + Element.DEVICES_FILE)) 84 | print(read(PATH("../Log/" + Element.DEVICES_FILE))) 85 | 86 | return 87 | 88 | 89 | # 统计各个设备成功失败的用例数 90 | def countSumDevices(devices, result, phone_name): 91 | _read = readInfo(PATH("../Log/" + Element.DEVICES_FILE)) 92 | if _read: 93 | for item in _read: 94 | if item["device"] == devices: # 本地已经存在该设备记录 95 | if result: 96 | item["pass"] = item["pass"] + 1 97 | else: 98 | item["fail"] = item["fail"] + 1 99 | write(data=_read, path=PATH("../Log/" + Element.DEVICES_FILE)) 100 | return 101 | countSumNoDevices(devices, result, _read, phone_name=phone_name) 102 | print(read(PATH("../Log/" + Element.DEVICES_FILE))) 103 | 104 | # else: 105 | # print("------0------") 106 | # countSumNoDevices(devices, result) 107 | # print("---countSumDevices---") 108 | # print(read(PATH("../Log/" + Element.DEVICES_FILE))) 109 | 110 | 111 | # 统计所有用例数 112 | def countSum(result): 113 | # print("----countSum----") 114 | data = {"sum": 0, "pass": 0, "fail": 0} 115 | _read = read(PATH("../Log/sum.pickle")) 116 | if _read: 117 | data = _read 118 | data["sum"] = data["sum"] + 1 119 | if result: 120 | data["pass"] = data["pass"] + 1 121 | else: 122 | data["fail"] = data["fail"] + 1 123 | write(data=data, path=PATH("../Log/" + Element.SUM_FILE)) 124 | # print(read(PATH("../Log/sum.pickle"))) 125 | 126 | 127 | # def write_reconnect(msg, path=""): 128 | # write(msg, path=path) 129 | # # print(read_reconnect(path)) 130 | 131 | 132 | def countDate(testDate, testSumDate): 133 | print("--------- countDate------") 134 | data = read(PATH("../Log/" + Element.SUM_FILE)) 135 | print(data) 136 | if data: 137 | data["testDate"] = testDate 138 | data["testSumDate"] = testSumDate 139 | write(data=data, path=PATH("../Log/" + Element.SUM_FILE)) 140 | else: 141 | print("统计数据失败") 142 | 143 | 144 | ''' 145 | 测试报告 146 | ''' 147 | 148 | 149 | def writeExcel(): 150 | workbook = xlsxwriter.Workbook(PATH('../Report/' + Element.REPORT_FILE)) 151 | worksheet = workbook.add_worksheet("测试总况") 152 | worksheet2 = workbook.add_worksheet("测试详情") 153 | operateReport = OperateReport(workbook) 154 | operateReport.init(worksheet, read(PATH("../Log/" + Element.SUM_FILE)), 155 | read(PATH("../Log/" + Element.DEVICES_FILE))) 156 | operateReport.detail(worksheet2, readInfo(PATH("../Log/" + Element.INFO_FILE))) 157 | operateReport.close() 158 | 159 | # destroy() # 删除文件 160 | 161 | 162 | if __name__ == '__main__': 163 | # data = {'result': '失败', 'caseName': 'FirstOpenTest', 'title': '第一次打开', 'phoneName': 'samsung_GT-I9500_android_4.4.2', 'img': 'D:\\app\\appium\\log\\samsung_GT-I9500_android_4.4.220170607184558\\第一次打开CheckPoint_1_NG.png', 'id': 'test001'} 164 | # writeInfo(data, PATH("../Log/info.pickle")) 165 | # writeInfo(data, PATH("../Log/info.pickle")) 166 | # _read = readInfo(PATH("../Log/info.pickle")) 167 | writeExcel() 168 | -------------------------------------------------------------------------------- /Base/BaseWebServer.py: -------------------------------------------------------------------------------- 1 | from http.server import BaseHTTPRequestHandler,HTTPServer 2 | from multiprocessing import Process 3 | import subprocess 4 | 5 | pid = 0 6 | class myHandler(BaseHTTPRequestHandler): 7 | def do_GET(self): 8 | d_result = {} 9 | find_devices="devices" 10 | req = self.path.split('&') 11 | if req[0].find(find_devices) > 0: 12 | d_result["devices"] = req[0].split("=")[1] 13 | d_result["log"] = req[1].split("=")[1] 14 | basePickle.write_pickle(d_result, gv.CRASH_LOG_PATH) 15 | self.send_response(200) 16 | self.send_header('Content-type','text/html') 17 | self.end_headers() 18 | # Send the html message 19 | self.wfile.write(b"Hello World !") #发送信息给客户端 20 | print("do_GET") 21 | def open_web_server(): 22 | server = HTTPServer((gv.HOST, gv.PORT), myHandler) 23 | print('Started httpserver on port ', gv.PORT) 24 | server.serve_forever() 25 | if __name__=="__main__": 26 | p = Process(target=open_web_server, args=()) 27 | p.start() 28 | # subprocess.Popen("taskkill /F /T /PID " + str(p.pid), shell=True) 29 | print("kkk") 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Base/BaseYaml.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import yaml 3 | from yaml.scanner import ScannerError 4 | import os 5 | 6 | 7 | def getYam(path): 8 | try: 9 | with open(path, encoding='utf-8') as f: 10 | x = yaml.load(f) 11 | return [True, x] 12 | except FileNotFoundError: 13 | print("==用例文件不存在==") 14 | app = {'check': [{'element_info': '', 'operate_type': 'get_value', 'find_type': 'ids', 'info': '用例文件不存在'}], 15 | 'testinfo': [{'title': '', 'id': '', 'info': '', "msg": ""}], 16 | 'testcase': [{'element_info': '', 'info': '', 'operate_type': '', 'find_type': ''}, 17 | {'element_info': '', 'msg': "", 'operate_type': '', 'find_type': '', 'info': ''}, 18 | {'element_info': '', 'msg': '', 'operate_type': '', 'find_type': '', 'info': ''}, 19 | {'element_info': '', 'info': '', 'operate_type': '', 'find_type': ''}]} 20 | 21 | return [False, app] 22 | except yaml.scanner.ScannerError: 23 | app = {'check': [{'element_info': '', 'operate_type': 'get_value', 'find_type': 'ids', 'info': '用例文件格式错误'}], 24 | 'testinfo': [{'title': '', 'id': '', 'info': '', "msg": " "}], 25 | 'testcase': [{'element_info': '', 'info': '', 'operate_type': '', 'find_type': ''}, 26 | {'element_info': '', 'msg': "", 'operate_type': '', 'find_type': '', 'info': ''}, 27 | {'element_info': '', 'msg': '', 'operate_type': '', 'find_type': '', 'info': ''}, 28 | {'element_info': '', 'info': '', 'operate_type': '', 'find_type': ''}]} 29 | print("==用例格式错误==") 30 | return [False, app] 31 | 32 | 33 | if __name__ == '__main__': 34 | import os 35 | 36 | PATH = lambda p: os.path.abspath( 37 | os.path.join(os.path.dirname(__file__), p) 38 | ) 39 | t = getYam(PATH("../yaml/test.yaml")) 40 | print(t) 41 | 42 | # port = str(random.randint(4700, 4900)) 43 | # bpport = str(random.randint(4700, 4900)) 44 | # devices = "DU2TAN15AJ049163" 45 | # 46 | # cmd1 = "appium --session-override -p %s -bp %s -U %s" % (port, bpport, devices) 47 | # print(cmd1) 48 | # os.popen(cmd1) 49 | -------------------------------------------------------------------------------- /Base/HTMLTestRunner.py: -------------------------------------------------------------------------------- 1 | """ 2 | A TestRunner for use with the Python unit testing framework. It 3 | generates a HTML report to show the result at a glance. 4 | 5 | The simplest way to use this is to invoke its main method. E.g. 6 | 7 | import unittest 8 | import HTMLTestRunner 9 | 10 | ... define your tests ... 11 | 12 | if __name__ == '__main__': 13 | HTMLTestRunner.main() 14 | 15 | 16 | For more customization options, instantiates a HTMLTestRunner object. 17 | HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g. 18 | 19 | # output to a file 20 | fp = file('my_report.html', 'wb') 21 | runner = HTMLTestRunner.HTMLTestRunner( 22 | stream=fp, 23 | title='My unit test', 24 | description='This demonstrates the report output by HTMLTestRunner.' 25 | ) 26 | 27 | # Use an external stylesheet. 28 | # See the Template_mixin class for more customizable options 29 | runner.STYLESHEET_TMPL = '' 30 | 31 | # run the test 32 | runner.run(my_test_suite) 33 | 34 | 35 | ------------------------------------------------------------------------ 36 | Copyright (c) 2004-2007, Wai Yip Tung 37 | All rights reserved. 38 | 39 | Redistribution and use in source and binary forms, with or without 40 | modification, are permitted provided that the following conditions are 41 | met: 42 | 43 | * Redistributions of source code must retain the above copyright notice, 44 | this list of conditions and the following disclaimer. 45 | * Redistributions in binary form must reproduce the above copyright 46 | notice, this list of conditions and the following disclaimer in the 47 | documentation and/or other materials provided with the distribution. 48 | * Neither the name Wai Yip Tung nor the names of its contributors may be 49 | used to endorse or promote products derived from this software without 50 | specific prior written permission. 51 | 52 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 53 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 54 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 55 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 56 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 57 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 58 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 59 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 60 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 61 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 62 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63 | """ 64 | 65 | # URL: http://tungwaiyip.info/software/HTMLTestRunner.html 66 | 67 | __author__ = "Wai Yip Tung" 68 | __version__ = "0.8.2" 69 | 70 | 71 | """ 72 | Change History 73 | 74 | Version 0.8.2 75 | * Show output inline instead of popup window (Viorel Lupu). 76 | 77 | Version in 0.8.1 78 | * Validated XHTML (Wolfgang Borgert). 79 | * Added description of test classes and test cases. 80 | 81 | Version in 0.8.0 82 | * Define Template_mixin class for customization. 83 | * Workaround a IE 6 bug that it does not treat 290 | 291 | %(heading)s 292 | %(report)s 293 | %(ending)s 294 | 295 | 296 | 297 | """ 298 | # variables: (title, generator, stylesheet, heading, report, ending) 299 | 300 | 301 | # ------------------------------------------------------------------------ 302 | # Stylesheet 303 | # 304 | # alternatively use a for external style sheet, e.g. 305 | # 306 | 307 | STYLESHEET_TMPL = """ 308 | 391 | """ 392 | 393 | 394 | 395 | # ------------------------------------------------------------------------ 396 | # Heading 397 | # 398 | 399 | HEADING_TMPL = """
400 |

%(title)s

401 | %(parameters)s 402 |

%(description)s

403 |
404 | 405 | """ # variables: (title, parameters, description) 406 | 407 | HEADING_ATTRIBUTE_TMPL = """

%(name)s: %(value)s

408 | """ # variables: (name, value) 409 | 410 | 411 | 412 | # ------------------------------------------------------------------------ 413 | # Report 414 | # 415 | 416 | REPORT_TMPL = """ 417 |

Show 418 | Summary 419 | Failed 420 | All 421 |

422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | %(test_list)s 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 |
Test Group/Test caseCountPassFailErrorView
Total%(count)s%(Pass)s%(fail)s%(error)s 
449 | """ # variables: (test_list, count, Pass, fail, error) 450 | 451 | REPORT_CLASS_TMPL = r""" 452 | 453 | %(desc)s 454 | %(count)s 455 | %(Pass)s 456 | %(fail)s 457 | %(error)s 458 | Detail 459 | 460 | """ # variables: (style, desc, count, Pass, fail, error, cid) 461 | 462 | 463 | REPORT_TEST_WITH_OUTPUT_TMPL = r""" 464 | 465 |
%(desc)s
466 | 467 | 468 | 469 | 470 | %(status)s 471 | 472 | 481 | 482 | 483 | 484 | 485 | """ # variables: (tid, Class, style, desc, status) 486 | 487 | 488 | REPORT_TEST_NO_OUTPUT_TMPL = r""" 489 | 490 |
%(desc)s
491 | %(status)s 492 | 493 | """ # variables: (tid, Class, style, desc, status) 494 | 495 | 496 | REPORT_TEST_OUTPUT_TMPL = r""" 497 | %(id)s: %(output)s 498 | """ # variables: (id, output) 499 | 500 | 501 | 502 | # ------------------------------------------------------------------------ 503 | # ENDING 504 | # 505 | 506 | ENDING_TMPL = """
 
""" 507 | 508 | # -------------------- The end of the Template class ------------------- 509 | 510 | 511 | TestResult = unittest.TestResult 512 | 513 | class _TestResult(TestResult): 514 | # note: _TestResult is a pure representation of results. 515 | # It lacks the output and reporting ability compares to unittest._TextTestResult. 516 | 517 | def __init__(self, verbosity=1): 518 | TestResult.__init__(self) 519 | self.stdout0 = None 520 | self.stderr0 = None 521 | self.success_count = 0 522 | self.failure_count = 0 523 | self.error_count = 0 524 | self.verbosity = verbosity 525 | 526 | # result is a list of result in 4 tuple 527 | # ( 528 | # result code (0: success; 1: fail; 2: error), 529 | # TestCase object, 530 | # Test output (byte string), 531 | # stack trace, 532 | # ) 533 | self.result = [] 534 | 535 | 536 | def startTest(self, test): 537 | TestResult.startTest(self, test) 538 | # just one buffer for both stdout and stderr 539 | self.outputBuffer = io.StringIO() 540 | stdout_redirector.fp = self.outputBuffer 541 | stderr_redirector.fp = self.outputBuffer 542 | self.stdout0 = sys.stdout 543 | self.stderr0 = sys.stderr 544 | sys.stdout = stdout_redirector 545 | sys.stderr = stderr_redirector 546 | 547 | 548 | def complete_output(self): 549 | """ 550 | Disconnect output redirection and return buffer. 551 | Safe to call multiple times. 552 | """ 553 | if self.stdout0: 554 | sys.stdout = self.stdout0 555 | sys.stderr = self.stderr0 556 | self.stdout0 = None 557 | self.stderr0 = None 558 | return self.outputBuffer.getvalue() 559 | 560 | 561 | def stopTest(self, test): 562 | # Usually one of addSuccess, addError or addFailure would have been called. 563 | # But there are some path in unittest that would bypass this. 564 | # We must disconnect stdout in stopTest(), which is guaranteed to be called. 565 | self.complete_output() 566 | 567 | 568 | def addSuccess(self, test): 569 | self.success_count += 1 570 | TestResult.addSuccess(self, test) 571 | output = self.complete_output() 572 | self.result.append((0, test, output, '')) 573 | if self.verbosity > 1: 574 | sys.stderr.write('ok ') 575 | sys.stderr.write(str(test)) 576 | sys.stderr.write('\n') 577 | else: 578 | sys.stderr.write('.') 579 | 580 | def addError(self, test, err): 581 | self.error_count += 1 582 | TestResult.addError(self, test, err) 583 | _, _exc_str = self.errors[-1] 584 | output = self.complete_output() 585 | self.result.append((2, test, output, _exc_str)) 586 | if self.verbosity > 1: 587 | sys.stderr.write('E ') 588 | sys.stderr.write(str(test)) 589 | sys.stderr.write('\n') 590 | else: 591 | sys.stderr.write('E') 592 | 593 | def addFailure(self, test, err): 594 | self.failure_count += 1 595 | TestResult.addFailure(self, test, err) 596 | _, _exc_str = self.failures[-1] 597 | output = self.complete_output() 598 | self.result.append((1, test, output, _exc_str)) 599 | if self.verbosity > 1: 600 | sys.stderr.write('F ') 601 | sys.stderr.write(str(test)) 602 | sys.stderr.write('\n') 603 | else: 604 | sys.stderr.write('F') 605 | 606 | 607 | class HTMLTestRunner(Template_mixin): 608 | """ 609 | """ 610 | def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): 611 | self.stream = stream 612 | self.verbosity = verbosity 613 | if title is None: 614 | self.title = self.DEFAULT_TITLE 615 | else: 616 | self.title = title 617 | if description is None: 618 | self.description = self.DEFAULT_DESCRIPTION 619 | else: 620 | self.description = description 621 | 622 | self.startTime = datetime.datetime.now() 623 | 624 | 625 | def run(self, test): 626 | "Run the given test case or test suite." 627 | result = _TestResult(self.verbosity) 628 | test(result) 629 | self.stopTime = datetime.datetime.now() 630 | self.generateReport(test, result) 631 | # print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime) 632 | print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)) 633 | return result 634 | 635 | 636 | def sortResult(self, result_list): 637 | # unittest does not seems to run in any particular order. 638 | # Here at least we want to group them together by class. 639 | rmap = {} 640 | classes = [] 641 | for n,t,o,e in result_list: 642 | cls = t.__class__ 643 | if not cls in rmap: 644 | rmap[cls] = [] 645 | classes.append(cls) 646 | rmap[cls].append((n,t,o,e)) 647 | r = [(cls, rmap[cls]) for cls in classes] 648 | return r 649 | 650 | 651 | def getReportAttributes(self, result): 652 | """ 653 | Return report attributes as a list of (name, value). 654 | Override this to add custom attributes. 655 | """ 656 | startTime = str(self.startTime)[:19] 657 | duration = str(self.stopTime - self.startTime) 658 | status = [] 659 | if result.success_count: status.append('Pass %s' % result.success_count) 660 | if result.failure_count: status.append('Failure %s' % result.failure_count) 661 | if result.error_count: status.append('Error %s' % result.error_count ) 662 | if status: 663 | status = ' '.join(status) 664 | else: 665 | status = 'none' 666 | return [ 667 | ('Start Time', startTime), 668 | ('Duration', duration), 669 | ('Status', status), 670 | ] 671 | 672 | 673 | def generateReport(self, test, result): 674 | report_attrs = self.getReportAttributes(result) 675 | generator = 'HTMLTestRunner %s' % __version__ 676 | stylesheet = self._generate_stylesheet() 677 | heading = self._generate_heading(report_attrs) 678 | report = self._generate_report(result) 679 | ending = self._generate_ending() 680 | output = self.HTML_TMPL % dict( 681 | title = saxutils.escape(self.title), 682 | generator = generator, 683 | stylesheet = stylesheet, 684 | heading = heading, 685 | report = report, 686 | ending = ending, 687 | ) 688 | self.stream.write(output.encode('utf8')) 689 | 690 | 691 | def _generate_stylesheet(self): 692 | return self.STYLESHEET_TMPL 693 | 694 | 695 | def _generate_heading(self, report_attrs): 696 | a_lines = [] 697 | for name, value in report_attrs: 698 | line = self.HEADING_ATTRIBUTE_TMPL % dict( 699 | name = saxutils.escape(name), 700 | value = saxutils.escape(value), 701 | ) 702 | a_lines.append(line) 703 | heading = self.HEADING_TMPL % dict( 704 | title = saxutils.escape(self.title), 705 | parameters = ''.join(a_lines), 706 | description = saxutils.escape(self.description), 707 | ) 708 | return heading 709 | 710 | 711 | def _generate_report(self, result): 712 | rows = [] 713 | sortedResult = self.sortResult(result.result) 714 | for cid, (cls, cls_results) in enumerate(sortedResult): 715 | # subtotal for a class 716 | np = nf = ne = 0 717 | for n,t,o,e in cls_results: 718 | if n == 0: np += 1 719 | elif n == 1: nf += 1 720 | else: ne += 1 721 | 722 | # format class description 723 | if cls.__module__ == "__main__": 724 | name = cls.__name__ 725 | else: 726 | name = "%s.%s" % (cls.__module__, cls.__name__) 727 | doc = cls.__doc__ and cls.__doc__.split("\n")[0] or "" 728 | desc = doc and '%s: %s' % (name, doc) or name 729 | 730 | row = self.REPORT_CLASS_TMPL % dict( 731 | style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', 732 | desc = desc, 733 | count = np+nf+ne, 734 | Pass = np, 735 | fail = nf, 736 | error = ne, 737 | cid = 'c%s' % (cid+1), 738 | ) 739 | rows.append(row) 740 | 741 | for tid, (n,t,o,e) in enumerate(cls_results): 742 | self._generate_report_test(rows, cid, tid, n, t, o, e) 743 | 744 | report = self.REPORT_TMPL % dict( 745 | test_list = ''.join(rows), 746 | count = str(result.success_count+result.failure_count+result.error_count), 747 | Pass = str(result.success_count), 748 | fail = str(result.failure_count), 749 | error = str(result.error_count), 750 | ) 751 | return report 752 | 753 | 754 | def _generate_report_test(self, rows, cid, tid, n, t, o, e): 755 | # e.g. 'pt1.1', 'ft1.1', etc 756 | has_output = bool(o or e) 757 | tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1) 758 | name = t.id().split('.')[-1] 759 | doc = t.shortDescription() or "" 760 | desc = doc and ('%s: %s' % (name, doc)) or name 761 | tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL 762 | 763 | # o and e should be byte string because they are collected from stdout and stderr? 764 | if isinstance(o,str): 765 | # TODO: some problem with 'string_escape': it escape \n and mess up formating 766 | # uo = unicode(o.encode('string_escape')) 767 | # uo = o.decode('latin-1') 768 | uo = e 769 | else: 770 | uo = o 771 | if isinstance(e,str): 772 | # TODO: some problem with 'string_escape': it escape \n and mess up formating 773 | # ue = unicode(e.encode('string_escape')) 774 | # ue = e.decode('latin-1') 775 | ue = e 776 | else: 777 | ue = e 778 | 779 | script = self.REPORT_TEST_OUTPUT_TMPL % dict( 780 | id = tid, 781 | output = saxutils.escape(str(uo)+ue), 782 | ) 783 | 784 | row = tmpl % dict( 785 | tid = tid, 786 | Class = (n == 0 and 'hiddenRow' or 'none'), 787 | style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), 788 | desc = desc, 789 | script = script, 790 | status = self.STATUS[n], 791 | ) 792 | rows.append(row) 793 | if not has_output: 794 | return 795 | 796 | def _generate_ending(self): 797 | return self.ENDING_TMPL 798 | 799 | 800 | ############################################################################## 801 | # Facilities for running tests from the command line 802 | ############################################################################## 803 | 804 | # Note: Reuse unittest.TestProgram to launch test. In the future we may 805 | # build our own launcher to support more specific command line 806 | # parameters like test title, CSS, etc. 807 | class TestProgram(unittest.TestProgram): 808 | """ 809 | A variation of the unittest.TestProgram. Please refer to the base 810 | class for command line parameters. 811 | """ 812 | def runTests(self): 813 | # Pick HTMLTestRunner as the default test runner. 814 | # base class's testRunner parameter is not useful because it means 815 | # we have to instantiate HTMLTestRunner before we know self.verbosity. 816 | if self.testRunner is None: 817 | self.testRunner = HTMLTestRunner(verbosity=self.verbosity) 818 | unittest.TestProgram.runTests(self) 819 | 820 | main = TestProgram 821 | 822 | ############################################################################## 823 | # Executing this module from the command line 824 | ############################################################################## 825 | 826 | if __name__ == "__main__": 827 | main(module=None) 828 | -------------------------------------------------------------------------------- /Base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__init__.py -------------------------------------------------------------------------------- /Base/__pycache__/BaseAdb.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseAdb.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseAndroidPhone.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseAndroidPhone.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseApk.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseApk.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseAppiumServer.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseAppiumServer.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseConfig.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseConfig.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseElementEnmu.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseElementEnmu.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseError.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseError.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseExcel.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseExcel.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseFile.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseFile.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseInit.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseInit.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseLog.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseLog.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseOperate.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseOperate.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BasePickle.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BasePickle.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseRunner.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseRunner.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseStatistics.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseStatistics.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/BaseYaml.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/BaseYaml.cpython-34.pyc -------------------------------------------------------------------------------- /Base/__pycache__/__init__.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Base/__pycache__/__init__.cpython-34.pyc -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/CHANGELOG.md -------------------------------------------------------------------------------- /Img/console.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Img/console.jpg -------------------------------------------------------------------------------- /Img/detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Img/detail.jpg -------------------------------------------------------------------------------- /Img/sum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Img/sum.png -------------------------------------------------------------------------------- /Log/sum.pickle: -------------------------------------------------------------------------------- 1 | (dp0 2 | VtestSumDate 3 | p1 4 | V45\u79d2 5 | p2 6 | sVversionCode 7 | p3 8 | V40 9 | p4 10 | sVsum 11 | p5 12 | L1L 13 | sVtestDate 14 | p6 15 | V2018-05-07 23:39:09 16 | p7 17 | sVfail 18 | p8 19 | L0L 20 | sVpackingTime 21 | p9 22 | V2017/12/4 13:00 23 | p10 24 | sVpass 25 | p11 26 | L1L 27 | sVversionName 28 | p12 29 | V1.4.0 30 | p13 31 | s. -------------------------------------------------------------------------------- /PageObject/Home/CardsSortPage.py: -------------------------------------------------------------------------------- 1 | from Base.BaseYaml import getYam 2 | from Base.BaseOperate import OperateElement 3 | from Base.BaseElementEnmu import Element as be 4 | from PageObject.SumResult import statistics_result 5 | 6 | 7 | class CardsSortPage: 8 | ''' 9 | 滑动删除历史记录 10 | isOperate: 操作失败,检查点就失败,kwargs: WebDriver driver, String path(yaml配置参数) 11 | ''' 12 | 13 | def __init__(self, kwargs): 14 | self.driver = kwargs["driver"] 15 | if kwargs.get("launch_app", "0") == "0": # 若为空,重新打开app 16 | self.driver.launch_app() 17 | self.path = kwargs["path"] 18 | self.operateElement = OperateElement(self.driver) 19 | self.isOperate = True 20 | test_msg = getYam(self.path) 21 | self.testInfo = test_msg["testinfo"] 22 | self.testCase = test_msg["testcase"] 23 | self.testcheck = test_msg["check"] 24 | self.device = kwargs["device"] 25 | self.logTest = kwargs["logTest"] 26 | self.caseName = kwargs["caseName"] 27 | self.get_value = [] 28 | self.location = [] 29 | self.msg = "" 30 | 31 | ''' 32 | 操作步骤 33 | logTest 日记记录器 34 | ''' 35 | 36 | def operate(self): 37 | for item in self.testCase: 38 | m_s_g = self.msg + "\n" if self.msg != "" else "" 39 | 40 | result = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 41 | if not result["result"]: 42 | msg = "执行过程中失败,请检查元素是否存在" + item["element_info"] 43 | print(msg) 44 | self.msg = m_s_g + msg 45 | self.testInfo[0]["msg"] = msg 46 | self.isOperate = False 47 | return False 48 | 49 | if item.get("operate_type", "0") == "location": 50 | app = {} 51 | web_element = self.driver.find_elements_by_id(item["element_info"])[item["index"]] 52 | start = web_element.location 53 | # 获取控件开始位置的坐标轴 54 | app["startX"] = start["x"] 55 | app["startY"] = start["y"] 56 | # 获取控件坐标轴差 57 | size1 = web_element.size 58 | 59 | width = size1["width"] 60 | height = size1["height"] 61 | # 计算出控件结束坐标 62 | endX = width + app["startX"] 63 | endY = height + app["startY"] 64 | 65 | app["endX"] = endX - 20 66 | app["endY"] = endY - 60 67 | 68 | self.location.append(app) 69 | # self.driver.swipe(endX, endY, starty, endY) 70 | if item.get("operate_type", "0") == be.GET_VALUE: 71 | self.get_value.append(result["text"]) 72 | 73 | if item.get("is_swpie", "0") != "0": 74 | print(self.location) 75 | self.driver.swipe(self.location[0]["endX"], self.location[0]["endY"], self.location[1]["endX"], self.location[1]["endY"]+10) 76 | 77 | return True 78 | 79 | def checkPoint(self, kwargs={}): 80 | result = self.check() 81 | if result is not True and be.RE_CONNECT: 82 | self.msg = "用例失败重连过一次,失败原因:" + self.testInfo[0]["msg"] 83 | self.logTest.buildStartLine(self.caseName + "_失败重连") # 记录日志 84 | self.operateElement.switchToNative() 85 | self.driver.launch_app() 86 | self.isOperate = True 87 | self.operate() 88 | self.get_value = [] 89 | self.location = "" 90 | result = self.check() 91 | self.testInfo[0]["msg"] = self.msg 92 | statistics_result(result=result, testInfo=self.testInfo, caseName=self.caseName, 93 | driver=self.driver, logTest=self.logTest, devices=self.device, 94 | testCase=self.testCase, 95 | testCheck=self.testcheck) 96 | return result 97 | 98 | ''' 99 | 检查点 100 | caseName:测试用例函数名 用作统计 101 | logTest: 日志记录 102 | devices 设备名 103 | contrary 相反检查点,意思就是如果检查结果为真,检查点就是失败 104 | ''' 105 | 106 | def check(self): 107 | result = True 108 | m_s_g = self.msg + "\n" if self.msg != "" else "" 109 | # 重跑后异常日志 110 | if self.isOperate: 111 | for item in self.testcheck: 112 | resp = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 113 | if not resp["result"]: 114 | msg = "请检查元素" + item["element_info"] + "是否存在" 115 | self.msg = m_s_g + msg 116 | print(msg) 117 | self.testInfo[0]["msg"] = msg 118 | result = False 119 | if resp['text'] not in self.get_value: # 删除后数据对比 120 | msg = "卡片排序失败" + str(self.get_value) + "当前首页第一条卡片数据为:" + resp["text"] + "排序成功的第一个卡片值为:"+".".join(self.get_value) 121 | self.msg = m_s_g + msg 122 | print(msg) 123 | self.testInfo[0]["msg"] = msg 124 | break 125 | else: 126 | result = False 127 | return result 128 | 129 | 130 | if __name__ == "__main__": 131 | pass 132 | -------------------------------------------------------------------------------- /PageObject/Home/CollectSwipeDellPage.py: -------------------------------------------------------------------------------- 1 | from Base.BaseYaml import getYam 2 | from Base.BaseOperate import OperateElement 3 | from Base.BaseElementEnmu import Element as be 4 | from PageObject.SumResult import statistics_result 5 | 6 | 7 | class CollectSwipeDelPage: 8 | ''' 9 | 滑动删除收藏 10 | isOperate: 操作失败,检查点就失败,kwargs: WebDriver driver, String path(yaml配置参数) 11 | ''' 12 | 13 | def __init__(self, kwargs): 14 | self.driver = kwargs["driver"] 15 | if kwargs.get("launch_app", "0") == "0": # 若为空,重新打开app 16 | self.driver.launch_app() 17 | self.path = kwargs["path"] 18 | self.operateElement = OperateElement(self.driver) 19 | self.isOperate = True 20 | test_msg = getYam(self.path) 21 | self.testInfo = test_msg["testinfo"] 22 | self.testCase = test_msg["testcase"] 23 | self.testcheck = test_msg["check"] 24 | self.device = kwargs["device"] 25 | self.logTest = kwargs["logTest"] 26 | self.caseName = kwargs["caseName"] 27 | self.get_value = [] 28 | self.msg = "" 29 | 30 | ''' 31 | 操作步骤 32 | logTest 日记记录器 33 | ''' 34 | 35 | def operate(self): 36 | m_s_g = self.msg + "\n" if self.msg != "" else "" 37 | for item in self.testCase: 38 | result = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 39 | if not result["result"]: 40 | msg = "执行过程中失败,请检查元素是否存在" + item["element_info"] 41 | print(msg) 42 | self.testInfo[0]["msg"] = msg 43 | self.msg = m_s_g + msg 44 | self.isOperate = False 45 | return False 46 | 47 | if item.get("operate_type", "0") == be.SWIPE_LEFT: # 根据元素左滑动 48 | web_element = self.driver.find_elements_by_id(item["element_info"])[item["index"]] 49 | start = web_element.location 50 | # 获取控件开始位置的坐标轴 51 | startx = start["x"] 52 | starty = start["y"] 53 | # 获取控件坐标轴差 54 | size1 = web_element.size 55 | 56 | width = size1["width"] 57 | height = size1["height"] 58 | # 计算出控件结束坐标 59 | endX = width + startx 60 | endY = height + starty 61 | self.driver.swipe(endX-50, endY, starty+500, endY) 62 | if item.get("operate_type", "0") == be.GET_VALUE: 63 | self.get_value.append(result["text"]) 64 | return True 65 | 66 | def checkPoint(self): 67 | result = self.check() 68 | if result is not True and be.RE_CONNECT: 69 | self.msg = "用例失败重连过一次,失败原因:" + self.testInfo[0]["msg"] 70 | self.logTest.buildStartLine(self.caseName + "_失败重连") # 记录日志 71 | # self.operateElement.switchToNative() 72 | self.driver.launch_app() 73 | self.isOperate = True 74 | self.get_value = [] 75 | self.operate() 76 | result = self.check() 77 | self.testInfo[0]["msg"] = self.msg 78 | self.operateElement.switchToNative() 79 | 80 | statistics_result(result=result, testInfo=self.testInfo, caseName=self.caseName, 81 | driver=self.driver, logTest=self.logTest, devices=self.device, 82 | testCase=self.testCase, 83 | testCheck=self.testcheck) 84 | return result 85 | 86 | ''' 87 | 检查点 88 | caseName:测试用例函数名 用作统计 89 | logTest: 日志记录 90 | devices 设备名 91 | ''' 92 | 93 | def check(self, kwargs={}): 94 | result = True 95 | m_s_g = self.msg + "\n" if self.msg != "" else "" 96 | 97 | if self.isOperate: 98 | for item in self.testcheck: 99 | resp = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 100 | 101 | if not resp["result"]: # 表示操作出现异常情况检查点为成功 102 | print("操作失败,简单点为成功") 103 | result = True 104 | break 105 | 106 | if resp["text"] in self.get_value: # 删除后数据对比 107 | msg = "删除数据失败,删除前数据为:" + ".".join(self.get_value) + "当前获取的数据为:" + resp["text"] 108 | self.msg = m_s_g + msg 109 | print(msg) 110 | self.testInfo[0]["msg"] = msg 111 | break 112 | else: 113 | result = False 114 | return result 115 | 116 | 117 | if __name__ == "__main__": 118 | pass 119 | -------------------------------------------------------------------------------- /PageObject/Home/FirstOpenPage.py: -------------------------------------------------------------------------------- 1 | from Base.BaseYaml import getYam 2 | from PageObject import Pages 3 | 4 | 5 | class FirstOpenPage: 6 | ''' 7 | Banner浏览历史 8 | isOperate: 操作失败,检查点就失败,kwargs: WebDriver driver, String path(yaml配置参数) 9 | ''' 10 | 11 | def __init__(self, kwargs): 12 | _init = {"driver": kwargs["driver"], "test_msg": getYam(kwargs["path"]), "device": kwargs["device"], 13 | "logTest": kwargs["logTest"], "caseName": kwargs["caseName"]} 14 | self.page = Pages.PagesObjects(_init) 15 | 16 | def operate(self): # 操作步骤 17 | self.page.operate() 18 | 19 | def checkPoint(self): # 检查点 20 | self.page.checkPoint() 21 | 22 | 23 | if __name__ == "__main__": 24 | pass 25 | -------------------------------------------------------------------------------- /PageObject/Home/HistorySwipeDellPage.py: -------------------------------------------------------------------------------- 1 | from Base.BaseYaml import getYam 2 | from Base.BaseOperate import OperateElement 3 | from Base.BaseElementEnmu import Element as be 4 | from PageObject.SumResult import statistics_result 5 | 6 | 7 | class HistorySwipeDelPage: 8 | ''' 9 | 滑动删除历史记录 10 | isOperate: 操作失败,检查点就失败,kwargs: WebDriver driver, String path(yaml配置参数) 11 | ''' 12 | 13 | def __init__(self, kwargs): 14 | self.driver = kwargs["driver"] 15 | if kwargs.get("launch_app", "0") == "0": # 若为空,重新打开app 16 | self.driver.launch_app() 17 | self.path = kwargs["path"] 18 | self.operateElement = OperateElement(self.driver) 19 | self.isOperate = True 20 | test_msg = getYam(self.path) 21 | self.testInfo = test_msg["testinfo"] 22 | self.testCase = test_msg["testcase"] 23 | self.testcheck = test_msg["check"] 24 | self.device = kwargs["device"] 25 | self.logTest = kwargs["logTest"] 26 | self.caseName = kwargs["caseName"] 27 | self.get_value = [] 28 | self.msg = "" 29 | 30 | ''' 31 | 操作步骤 32 | logTest 日记记录器 33 | ''' 34 | 35 | def operate(self): 36 | for item in self.testCase: 37 | 38 | result = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 39 | if not result["result"]: 40 | msg = "执行过程中失败,请检查元素是否存在" + item["element_info"] 41 | m_s_g = self.msg + "\n" if self.msg != "" else "" 42 | self.msg = m_s_g + msg 43 | print(msg) 44 | self.testInfo[0]["msg"] = msg 45 | self.isOperate = False 46 | return False 47 | 48 | if item.get("operate_type", "0") == be.SWIPE_LEFT: # 根据元素左滑动 49 | web_element = self.driver.find_elements_by_id(item["element_info"])[item["index"]] 50 | start = web_element.location 51 | # 获取控件开始位置的坐标轴 52 | startx = start["x"] 53 | starty = start["y"] 54 | # 获取控件坐标轴差 55 | size1 = web_element.size 56 | 57 | width = size1["width"] 58 | height = size1["height"] 59 | # 计算出控件结束坐标 60 | endX = width + startx 61 | endY = height + starty 62 | self.driver.swipe(endX, endY, starty, endY) 63 | if item.get("operate_type", "0") == be.GET_VALUE: 64 | self.get_value.append(result['text']) 65 | return True 66 | 67 | def checkPoint(self, kwargs={}): 68 | result = self.check() 69 | if result is not True and be.RE_CONNECT: 70 | self.msg = "用例失败重连过一次,失败原因:" + self.testInfo[0]["msg"] 71 | self.logTest.buildStartLine(self.caseName + "_失败重连") # 记录日志 72 | self.operateElement.switchToNative() 73 | self.driver.launch_app() 74 | self.isOperate = True 75 | self.get_value = [] 76 | self.operate() 77 | result = self.check() 78 | self.testInfo[0]["msg"] = self.msg 79 | statistics_result(result=result, testInfo=self.testInfo, caseName=self.caseName, 80 | driver=self.driver, logTest=self.logTest, devices=self.device, 81 | testCase=self.testCase, 82 | testCheck=self.testcheck) 83 | return result 84 | 85 | ''' 86 | 检查点 87 | caseName:测试用例函数名 用作统计 88 | logTest: 日志记录 89 | devices 设备名 90 | ''' 91 | 92 | def check(self, kwargs={}): 93 | result = True 94 | m_s_g = self.msg + "\n" if self.msg != "" else "" 95 | # 重跑后异常日志 96 | 97 | if self.isOperate: 98 | for item in self.testcheck: 99 | resp = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 100 | if not resp["result"]: 101 | msg = "请检查元素" + item["element_info"] + "是否存在" 102 | self.msg = m_s_g + msg 103 | print(msg) 104 | self.testInfo[0]["msg"] = msg 105 | result = False 106 | if resp["text"] in self.get_value: # 删除后数据对比 107 | msg = "删除数据失败,删除前数据为:" + ".".join(self.get_value) + "当前获取的数据为:" + resp["text"] 108 | self.msg = m_s_g + msg 109 | print(msg) 110 | self.testInfo[0]["msg"] = msg 111 | break 112 | else: 113 | result = False 114 | return result 115 | 116 | 117 | if __name__ == "__main__": 118 | pass 119 | -------------------------------------------------------------------------------- /PageObject/Home/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/Home/__init__.py -------------------------------------------------------------------------------- /PageObject/Home/__pycache__/FirstOpenPage.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/Home/__pycache__/FirstOpenPage.cpython-34.pyc -------------------------------------------------------------------------------- /PageObject/Home/__pycache__/__init__.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/Home/__pycache__/__init__.cpython-34.pyc -------------------------------------------------------------------------------- /PageObject/Pages.py: -------------------------------------------------------------------------------- 1 | from Base.BaseError import get_error 2 | from Base.BaseYaml import getYam 3 | from Base.BaseOperate import OperateElement 4 | import time 5 | from Base.BaseElementEnmu import Element as be 6 | import os 7 | from PageObject.SumResult import statistics_result 8 | 9 | PATH = lambda p: os.path.abspath( 10 | os.path.join(os.path.dirname(__file__), p) 11 | ) 12 | 13 | 14 | class PagesObjects: 15 | ''' 16 | page层 17 | kwargs: WebDriver driver, String path(yaml配置参数) 18 | isOperate: 操作失败,检查点就失败 19 | testInfo: 20 | testCase: 21 | ''' 22 | 23 | def __init__(self, kwargs): 24 | self.driver = kwargs["driver"] 25 | if kwargs.get("launch_app", "0") == "0": # 若为空,重新打开app 26 | self.driver.launch_app() 27 | # self.path = kwargs["path"] 28 | self.operateElement = OperateElement(self.driver) 29 | self.isOperate = True 30 | self.test_msg = kwargs["test_msg"] 31 | self.testInfo = self.test_msg[1]["testinfo"] 32 | self.testCase = self.test_msg[1]["testcase"] 33 | self.testcheck = self.test_msg[1]["check"] 34 | self.device = kwargs["device"] 35 | self.logTest = kwargs["logTest"] 36 | self.caseName = kwargs["caseName"] 37 | self.get_value = [] 38 | self.is_get = False # 检查点特殊标志,结合get_value使用。若为真,说明检查点要对比历史数据和实际数据 39 | self.msg = "" 40 | 41 | ''' 42 | 操作步骤 43 | ''' 44 | 45 | def operate(self): 46 | if self.test_msg[0] is False: # 如果用例编写错误 47 | self.isOperate = False 48 | return False 49 | for item in self.testCase: 50 | m_s_g = self.msg + "\n" if self.msg != "" else "" 51 | result = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 52 | if not result["result"]: 53 | msg = "执行过程中失败,请检查元素是否存在" + item["element_info"] + "," + result.get("text", " ") 54 | if not result.get("webview", True): 55 | msg = "切换到webview失败,请确定是否在webview页面" 56 | print(msg) 57 | self.msg = m_s_g + msg 58 | self.testInfo[0]["msg"] = msg 59 | self.isOperate = False 60 | return False 61 | if item.get("is_time", "0") != "0": 62 | time.sleep(item["is_time"]) # 等待时间 63 | print("--等待下---") 64 | 65 | if item.get("operate_type", "0") == be.GET_VALUE or item.get("operate_type", "0") == be.GET_CONTENT_DESC: 66 | self.get_value.append(result["text"]) 67 | self.is_get = True # 对比数据 68 | 69 | return True 70 | 71 | def checkPoint(self, kwargs={}): 72 | result = self.check(kwargs) 73 | if self.test_msg[0] is not False: # 如果用例编写正确 74 | if result is not True and be.RE_CONNECT: 75 | self.msg = "用例失败重连过一次,失败原因:" + self.testInfo[0]["msg"] 76 | self.logTest.buildStartLine(self.caseName + "_失败重连") # 记录日志 77 | self.operateElement.switchToNative() 78 | self.driver.launch_app() 79 | self.isOperate = True 80 | self.get_value = [] 81 | self.is_get = False 82 | self.operate() 83 | result = self.check(kwargs) 84 | self.testInfo[0]["msg"] = self.msg 85 | self.operateElement.switchToNative() 86 | 87 | statistics_result(result=result, testInfo=self.testInfo, caseName=self.caseName, 88 | driver=self.driver, logTest=self.logTest, devices=self.device, 89 | testCase=self.testCase, 90 | testCheck=self.testcheck) 91 | 92 | ''' 93 | 检查点 94 | caseName:测试用例函数名 用作统计 95 | logTest: 日志记录 96 | devices 设备名 97 | contrary:相反检查点,传1表示如果检查元素存在就说明失败 98 | toast: 表示提示框检查点 99 | contrary_getval: 相反值检查点,如果对比成功,说明失败 100 | check_point: 自定义检查结果 101 | ''' 102 | 103 | def check(self, kwargs): 104 | result = True 105 | m_s_g = self.msg + "\n" if self.msg != "" else "" 106 | # 如果有重跑机制,成功后会默认把日志传进来 107 | 108 | 109 | # if kwargs.get("check_point", "0") != "0": 自定义检查点 110 | # return kwargs["check_point"] 111 | 112 | if self.isOperate: 113 | for item in self.testcheck: 114 | if kwargs.get("check", be.DEFAULT_CHECK) == be.TOAST: 115 | result = \ 116 | self.operateElement.toast(item["element_info"], testInfo=self.testInfo, logTest=self.logTest)[ 117 | "result"] 118 | if result is False: 119 | m = get_error( 120 | {"type": be.DEFAULT_CHECK, "element_info": item["element_info"], "info": item["info"]}) 121 | self.msg = m_s_g + m 122 | print(m) 123 | self.testInfo[0]["msg"] = m 124 | break 125 | else: 126 | resp = self.operateElement.operate(item, self.testInfo, self.logTest, self.device) 127 | 128 | if kwargs.get("check", be.DEFAULT_CHECK) == be.DEFAULT_CHECK and not resp["result"]: 129 | m = get_error( 130 | {"type": be.DEFAULT_CHECK, "element_info": item["element_info"], "info": item["info"]}) 131 | self.msg = m_s_g + m 132 | print(m) 133 | self.testInfo[0]["msg"] = m 134 | result = False 135 | break 136 | else: 137 | result = False 138 | return result 139 | -------------------------------------------------------------------------------- /PageObject/SumResult.py: -------------------------------------------------------------------------------- 1 | from Base.BaseAndroidPhone import getPhoneInfo 2 | from Base.BaseStatistics import countSum, countInfo, countSumDevices 3 | 4 | 5 | def statistics_result(**kwargs): 6 | countSum(kwargs["result"]) 7 | get_phone = getPhoneInfo(kwargs["devices"]) 8 | phone_name = get_phone["brand"] + "_" + get_phone["model"] + "_" + "android" + "_" + get_phone["release"] 9 | 10 | countInfo(result=kwargs["result"], testInfo=kwargs["testInfo"], caseName=kwargs["caseName"], phoneName=phone_name, 11 | driver=kwargs["driver"], logTest=kwargs["logTest"], devices=kwargs["devices"], testCase=kwargs["testCase"], 12 | testCheck=kwargs["testCheck"]) 13 | countSumDevices(kwargs["devices"], kwargs["result"], phone_name=phone_name) 14 | -------------------------------------------------------------------------------- /PageObject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/__init__.py -------------------------------------------------------------------------------- /PageObject/__pycache__/Pages.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/__pycache__/Pages.cpython-34.pyc -------------------------------------------------------------------------------- /PageObject/__pycache__/SumResult.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/__pycache__/SumResult.cpython-34.pyc -------------------------------------------------------------------------------- /PageObject/__pycache__/__init__.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/PageObject/__pycache__/__init__.cpython-34.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目名及简介 2 | * 基于appium+python3封装的自动化测试框架 3 | 4 | # 介绍 5 | * unittest参数化 6 | * PageObject分层管理 7 | * 本地化多机日志管理 8 | * 用例编写基于yaml配置多关键字驱动 9 | * 支持多设备andoird并行 10 | * 支持失败重连 11 | * 自动生成excel测试报告 12 | 13 | 14 | 15 | ## 命令运行 16 | 17 | ``` 18 | python runner.py 19 | ``` 20 | 21 | 22 | # 结果展示 23 | 24 | **日志目录** 25 | 26 | 文件夹:samsung_GT-I9500_android_4.4,包含截图 27 | 28 | ``` 29 | 2017-06-07 19:39:35,972 - INFO - ---- test001_第一次打开_android.widget.ImageView START ---- 30 | 2017-06-07 19:39:44,433 - INFO - [CheckPoint_1]: FirstOpenTest: NG 31 | 2017-06-07 19:40:02,013 - INFO - ---- test0002_登录_com.jianshu.haruki:id/btn_login START ---- 32 | 2017-06-07 19:40:03,075 - INFO - ---- test0002_登录_com.jianshu.haruki:id/et_tel START ---- 33 | 2017-06-07 19:40:07,460 - INFO - ---- test0002_登录_com.jianshu.haruki:id/et_password START ---- 34 | 2017-06-07 19:40:08,480 - INFO - ---- test0002_登录_com.jianshu.haruki:id/btn_register_1 START ---- 35 | 2017-06-07 19:40:13,640 - INFO - ---- test0002_登录_//android.widget.ImageView[@index='0'] START ---- 36 | 2017-09-23 17:28:26,074 - INFO - [CheckPoint_1]: TechZoneDetailTest_请检查元素//*[@id="app"]/div/div[2]/section[2]/div[1]/div是否存在: NG 37 | ``` 38 | 39 | 40 | 41 | **测试报告** 42 | 43 | ![sum.png](Img/sum.png "sum.png") 44 | 45 | ![detail.jpg](Img/detail.jpg "detail.jpg") 46 | 47 | # 其他 48 | * [测试用例关键字驱动说明](mark.md) 49 | * [使用实例](use.md) 50 | * [changelog](CHANGELOG.md) 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Report/Report.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Report/Report.xlsx -------------------------------------------------------------------------------- /Runner/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/Runner/__init__.py -------------------------------------------------------------------------------- /Runner/runner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'shikun' 4 | import sys 5 | 6 | sys.path.append("..") 7 | import platform 8 | from Base.BaseAndroidPhone import * 9 | from Base.BaseAdb import * 10 | from Base.BaseRunner import ParametrizedTestCase 11 | from TestCase.HomeTest import HomeTest 12 | from Base.BaseAppiumServer import AppiumServer 13 | from multiprocessing import Pool 14 | import unittest 15 | from Base.BaseInit import init, mk_file 16 | from Base.BaseStatistics import countDate, writeExcel, countSumDevices 17 | from Base.BasePickle import * 18 | from datetime import datetime 19 | from Base.BaseApk import ApkInfo 20 | PATH = lambda p: os.path.abspath( 21 | os.path.join(os.path.dirname(__file__), p) 22 | ) 23 | 24 | 25 | def kill_adb(): 26 | if platform.system() == "Windows": 27 | # os.popen("taskkill /f /im adb.exe") 28 | os.system(PATH("../app/kill5037.bat")) 29 | else: 30 | os.popen("killall adb") 31 | os.system("adb start-server") 32 | 33 | def runnerPool(getDevices): 34 | devices_Pool = [] 35 | 36 | for i in range(0, len(getDevices)): 37 | _pool = [] 38 | _initApp = {} 39 | _initApp["deviceName"] = getDevices[i]["devices"] 40 | _initApp["platformVersion"] = getPhoneInfo(devices=_initApp["deviceName"])["release"] 41 | _initApp["platformName"] = "android" 42 | _initApp["port"] = getDevices[i]["port"] 43 | _initApp["automationName"] = "uiautomator2" 44 | _initApp["systemPort"] = getDevices[i]["systemPort"] 45 | 46 | _initApp["app"] = getDevices[i]["app"] 47 | apkInfo = ApkInfo(_initApp["app"]) 48 | _initApp["appPackage"] = apkInfo.getApkBaseInfo()[0] 49 | _initApp["appActivity"] = apkInfo.getApkActivity() 50 | _pool.append(_initApp) 51 | devices_Pool.append(_initApp) 52 | 53 | pool = Pool(len(devices_Pool)) 54 | pool.map(runnerCaseApp, devices_Pool) 55 | pool.close() 56 | pool.join() 57 | 58 | 59 | def runnerCaseApp(devices): 60 | starttime = datetime.now() 61 | suite = unittest.TestSuite() 62 | suite.addTest(ParametrizedTestCase.parametrize(HomeTest, param=devices)) 63 | # suite.addTest(ParametrizedTestCase.parametrize(HomeTest, param=devices)) #加入测试类 64 | unittest.TextTestRunner(verbosity=2).run(suite) 65 | endtime = datetime.now() 66 | countDate(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str((endtime - starttime).seconds) + "秒") 67 | 68 | if __name__ == '__main__': 69 | 70 | kill_adb() 71 | 72 | devicess = AndroidDebugBridge().attached_devices() 73 | if len(devicess) > 0: 74 | mk_file() 75 | l_devices = [] 76 | for dev in devicess: 77 | app = {} 78 | app["devices"] = dev 79 | init(dev) 80 | app["port"] = str(random.randint(4700, 4900)) 81 | app["bport"] = str(random.randint(4700, 4900)) 82 | app["systemPort"] = str(random.randint(4700, 4900)) 83 | app["app"] = PATH("../app/com.ximalaya.ting.android.apk") # 测试的app路径,喜马拉雅app 84 | 85 | l_devices.append(app) 86 | 87 | appium_server = AppiumServer(l_devices) 88 | appium_server.start_server() 89 | runnerPool(l_devices) 90 | writeExcel() 91 | appium_server.stop_server(l_devices) 92 | else: 93 | print("没有可用的安卓设备") 94 | -------------------------------------------------------------------------------- /Runner/runner_ios.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'shikun' 4 | import sys 5 | import random 6 | 7 | sys.path.append("..") 8 | import platform 9 | from Base.BaseAndroidPhone import * 10 | from Base.BaseIosCommand import * 11 | from Base.BaseRunner import ParametrizedTestCase 12 | from TestCase.HomeTest import HomeTest 13 | from TestCase.ContactTest import ContactTest 14 | from TestCase.CardsTest import CardsTest 15 | from TestCase.MeTest import MeTest 16 | from TestCase.HistoryTest import HistoryTest 17 | from TestCase.TeamTest import TeamTest 18 | from TestCase.TestWeiQunTest import TestWeiQunTest 19 | from Base.BaseAppiumServer import AppiumServer 20 | from multiprocessing import Pool 21 | import unittest 22 | from Base.BaseInit import init 23 | from Base.BaseStatistics import countDate, writeExcel 24 | from Base.BasePickle import * 25 | from datetime import datetime 26 | 27 | PATH = lambda p: os.path.abspath( 28 | os.path.join(os.path.dirname(__file__), p) 29 | ) 30 | 31 | 32 | def runnerPool(getDevices): 33 | devices_Pool = [] 34 | 35 | for i in range(0, len(getDevices)): 36 | _pool = [] 37 | print("----runnerPool------") 38 | print(getDevices[i]) 39 | _initApp = {} 40 | devices = getDevices[i]["devices"] 41 | _initApp["deviceName"] = get_ios_product_name(devices) 42 | _initApp["platformVersion"] = get_ios_version(devices) 43 | _initApp["platformName"] = "ios" 44 | _initApp["port"] = getDevices[i]["port"] 45 | _initApp["bundleId"] = "com.huawei.works" 46 | _initApp["udid"] = devices 47 | _initApp["automationName"] = "XCUITest" 48 | _pool.append(_initApp) 49 | devices_Pool.append(_initApp) 50 | 51 | pool = Pool(len(devices_Pool)) 52 | pool.map(runnerCaseApp, devices_Pool) 53 | pool.close() 54 | pool.join() 55 | 56 | 57 | def runnerCaseApp(devices): 58 | starttime = datetime.now() 59 | suite = unittest.TestSuite() 60 | 61 | suite.addTest(ParametrizedTestCase.parametrize(HomeTest, param=devices)) 62 | # suite.addTest(ParametrizedTestCase.parametrize(TestWeiQunTest, param=devices)) 63 | # suite.addTest(ParametrizedTestCase.parametrize(HistoryTest, param=devices)) 64 | # suite.addTest(ParametrizedTestCase.parametrize(ContactTest, param=devices)) 65 | # suite.addTest(ParametrizedTestCase.parametrize(MeTest, param=devices)) 66 | # suite.addTest(ParametrizedTestCase.parametrize(CardsTest, param=devices)) 67 | # suite.addTest(ParametrizedTestCase.parametrize(TeamTest, param=devices)) 68 | unittest.TextTestRunner(verbosity=2).run(suite) 69 | endtime = datetime.now() 70 | countDate(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str((endtime - starttime).seconds) + "秒") 71 | 72 | 73 | if __name__ == '__main__': 74 | 75 | devicess = get_ios_devices() 76 | if len(devicess) > 0: 77 | l_devices = [] 78 | init() 79 | 80 | for dev in devicess: 81 | app = {} 82 | app["devices"] = dev 83 | app["port"] = str(random.randint(4700, 4900)) 84 | app["bport"] = str(random.randint(4700, 4900)) 85 | l_devices.append(app) 86 | 87 | appium_server = AppiumServer(l_devices) 88 | appium_server.start_server() 89 | runnerPool(l_devices) 90 | writeExcel() 91 | appium_server.stop_server(l_devices) 92 | else: 93 | print("没有可用的安卓设备") 94 | -------------------------------------------------------------------------------- /TestCase/HomeTest.py: -------------------------------------------------------------------------------- 1 | 2 | from Base.BaseRunner import ParametrizedTestCase 3 | import os 4 | import sys 5 | from PageObject.Home.FirstOpenPage import FirstOpenPage 6 | PATH = lambda p: os.path.abspath( 7 | os.path.join(os.path.dirname(__file__), p) 8 | ) 9 | 10 | 11 | class HomeTest(ParametrizedTestCase): 12 | def testFirstOpen(self): 13 | app = {"logTest": self.logTest, "driver": self.driver, "path": PATH("../yamls/home/firstOpen.yaml"), 14 | "device": self.devicesName, "caseName": sys._getframe().f_code.co_name} 15 | 16 | page = FirstOpenPage(app) 17 | page.operate() 18 | page.checkPoint() 19 | 20 | 21 | 22 | @classmethod 23 | def setUpClass(cls): 24 | super(HomeTest, cls).setUpClass() 25 | 26 | @classmethod 27 | def tearDownClass(cls): 28 | super(HomeTest, cls).tearDownClass() 29 | -------------------------------------------------------------------------------- /TestCase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/TestCase/__init__.py -------------------------------------------------------------------------------- /TestCase/__pycache__/HomeTest.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/TestCase/__pycache__/HomeTest.cpython-34.pyc -------------------------------------------------------------------------------- /TestCase/__pycache__/__init__.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/TestCase/__pycache__/__init__.cpython-34.pyc -------------------------------------------------------------------------------- /app/android-system-webview-60.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/app/android-system-webview-60.apk -------------------------------------------------------------------------------- /app/appium-uiautomator2-server-debug-androidTest.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/app/appium-uiautomator2-server-debug-androidTest.apk -------------------------------------------------------------------------------- /app/appium-uiautomator2-server-v0.1.9.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/app/appium-uiautomator2-server-v0.1.9.apk -------------------------------------------------------------------------------- /app/kill5037.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | color a 3 | title ReleaseAdbPort 4 | echo ====================== 5 | echo *** liyu 2015-01-15*** 6 | echo *** v1.0.0 *** 7 | echo ====================== 8 | echo --------------------------- 9 | echo Checking adb port... 10 | for /F "usebackq tokens=5" %%a in (`"netstat -ano | findstr "5037""`) do ( 11 | if not "%%a" =="0" call :ReleasePort %%a 12 | ) 13 | echo --------------------------- 14 | echo adb port has been released! 15 | echo --------------------------- 16 | 17 | 18 | exit 19 | 20 | :ReleasePort 21 | TASKKILL /f /PID %1 22 | 23 | exit -------------------------------------------------------------------------------- /doc/Appium_Android.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/doc/Appium_Android.docx -------------------------------------------------------------------------------- /doc/Appium_IOS.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/doc/Appium_IOS.docx -------------------------------------------------------------------------------- /doc/~$pium_Android.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/doc/~$pium_Android.docx -------------------------------------------------------------------------------- /doc/自动化框架使用说明.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/doc/自动化框架使用说明.docx -------------------------------------------------------------------------------- /mark.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/mark.md -------------------------------------------------------------------------------- /use.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Louis-me/appium/8ebede253e08c71a00273b2ab8411bf983d01469/use.md -------------------------------------------------------------------------------- /yamls/home/firstOpen.yaml: -------------------------------------------------------------------------------- 1 | testinfo: 2 | - id: test001 3 | title: 第一次用例 4 | info: 打开app 5 | testcase: 6 | - element_info: //*[@text="跳过广告"] 7 | find_type: xpath 8 | operate_type: click 9 | info: 跳过广告 10 | - element_info: //*[@text="账号"] 11 | find_type: xpath 12 | operate_type: click 13 | info: 点击账号 14 | - element_info: //*[@text="我的钱包"] 15 | find_type: xpath 16 | operate_type: click 17 | info: 我的钱包 18 | - bounds: [[0,1217],[1080,1337]] 19 | operate_type: tap 20 | duration: 100 21 | info: 用坐标点击优惠券 22 | check: 23 | - blouns: //*[@text="未使用"] 24 | find_type: xpath 25 | info: 打开页面成功 26 | --------------------------------------------------------------------------------