class err_resetting(object): def __init__(self,func): self.info = Information() self.func = func def __call__(self, *args, **kwargs): try: a = self.func(*args, **kwargs) except NotPocoServeException: pristr = "poco断开,游戏" + str(self.info.game_is_running()) if not self.info.game_is_running(): game_name = self.info.get_config("App_Name", "game_name") start_app(game_name) sleep(2) snapshot(msg=pristr) snapshot(msg=pristr) # self.poco = StdPoco() # todo 看看游戏poco服重启时候是否OK # self.set_poco(self.poco) a = self.func(*args, **kwargs) return a except NoneException: snapshot(msg="找不到控件") raise NoneException except Exception as e : pristr = "报错,游戏" + str(self.info.game_is_running()) snapshot(msg=pristr) raise e
class NewAccount: def __init__(self): self.info = Information() self.game_name = self.info.get_config("App_Name", "game_name") if self.game_name == "com.youzu.test.qa": self.nas = NewAccountSs2() else: pass def new_game_account(self, resource_dic_input, sever_name_input, play_dic): # 接口方法,后期拓展游戏使用 """ 创建一个新账号 :param dic_input: 账号要求,字典 :param sever_name_input: 区服名 :return: """ if self.game_name == "com.youzu.test.qa": # ss2 self.nas.new_account_ss2(resource_dic_input, sever_name_input, play_dic) else: pass
class GetPocoDic(object): """safe and exact recv & send""" def __init__(self, game_name, phone_name="", on_connect=None, on_close=None): """address is (host, port) tuple""" self.game_name = game_name self.info = Information() self.phone_name = phone_name self.on_connect = on_connect self.on_close = on_close self.sock = None self.buf = b"" self.prot = SimpleProtocolFilter() self.device = None or current_device() if not self.device and self.phone_name != None: self.device = connect_device("Android:///" + self.phone_name) self.platform_name = device_platform(self.device) self.connect_phone() self.address = (self.ip, self.port) def get_phone_size(self): ''' 获取手机分辨率和其他信息 :return: ''' phone_size = self.device.adb.get_display_info() return phone_size def get_device_adb_shell(self, cmd): """ 读取手机cmd数据,加入了指定设备号 :param cmd: :return: """ if self.phone_name == "": cmd_in = "adb " + cmd else: cmd_in = "adb -s " + self.phone_name + " " + cmd game_activity_list = os.popen(cmd_in) game_activity_str = str(game_activity_list.readlines()) return game_activity_str def connect_phone(self): sever_poco = self.info.get_config(self.game_name, "sever_poco") if sever_poco == "cocos-lua": self.port = COCOSLUA_PORT elif sever_poco == "unity": self.port = UNITY_PORT if self.platform_name == 'Android': # always forward for android device to avoid network unreachable local_port, _ = self.device.adb.setup_forward('tcp:{}'.format( self.port)) self.ip = self.device.adb.host or 'localhost' self.port = local_port elif self.platform_name == 'IOS': # ip = device.get_ip_address() # use iproxy first self.ip = 'localhost' local_port, _ = self.device.instruct_helper.setup_proxy(self.port) self.port = local_port else: try: self.ip = self.device.get_ip_address() except AttributeError: try: self.ip = socket.gethostbyname(socket.gethostname()) except socket.gaierror: # 某些特殊情况下会出现这个error,无法正确获取本机ip地址 self.ip = 'localhost' def connect(self): # create a new socket every time self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(DEFAULT_TIMEOUT) self.sock.connect(self.address) self._handle_connect() def send(self, msg): totalsent = 0 while totalsent < len(msg): sent = self.sock.send(msg) if sent == 0: self._handle_close() raise socket.error("socket connection broken") totalsent += sent def recv(self, size=DEFAULT_SIZE): trunk = self.sock.recv(size) if trunk == b'': self._handle_close() raise socket.error("socket connection broken") return trunk def recv_all(self, size): while len(self.buf) < size: trunk = self.recv(min(size - len(self.buf), DEFAULT_SIZE)) self.buf += trunk ret, self.buf = self.buf[:size], self.buf[size:] return ret def settimeout(self, timeout): self.sock.settimeout(timeout) def recv_nonblocking(self, size): self.sock.settimeout(0) try: ret = self.recv(size) except socket.error as e: # 10035 no data when nonblocking if e.args[ 0] == 10035: # errno.EWOULDBLOCK: errno is not always right ret = None # 10053 connection abort by client # 10054 connection reset by peer elif e.args[0] in [10053, 10054]: # errno.ECONNABORTED: raise else: raise return ret def close(self): self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() self._handle_close() def _handle_connect(self): if callable(self.on_connect): self.on_connect() def _handle_close(self): if callable(self.on_close): self.on_close() def get_ui_tree(self, dic, new_poco_dic): # 用于新poco规则 """ 遍历节点,生成字典{name:payload} :param dic: 根节点字典 :param new_poco_dic: 生成的字典,贯穿整个UI树 :return: new_poco_dic """ dics = dic keys = dic.keys() if 'node' in keys: # 第一次进来 dics = dic['node'] keys = dics.keys() if "name" in dics.keys(): this_name = dics['name'] new_poco_dic[this_name] = dics['payload'] if "text" in dics['payload'].keys(): # 获取text内容 this_text = dics['payload']['text'] if this_text != "": # text作为key # 名字、text都在元素列表里面 new_poco_dic[this_text] = dics['payload'] if 'children' in keys: childs_list = dics['children'] for i in range(len(childs_list)): childs_dic = childs_list[i] new_poco_dic = self.get_ui_tree(childs_dic, new_poco_dic) return new_poco_dic def get_poco_dic(self): start_time = time.time() b = {"method": "Dump", "params": [True], "jsonrpc": "2.0", "id": ""} b["id"] = six.text_type(uuid.uuid4()) b = json.dumps(b) self.connect() msg_bytes = self.prot.pack(b) self.send(msg_bytes) self.buf = b'' while True: try: ret = self.recv() self.buf = self.buf + ret length = struct.unpack('i', self.buf[0:HEADER_SIZE]) if (len(self.buf) - HEADER_SIZE) == length[0]: break except Exception: break # self.buf = self.buf + ret if self.buf != b'': ret = self.prot.unpack(self.buf) poco_path_dic_byte = ret[1] poco_path_dic_str = str(poco_path_dic_byte, encoding="utf-8") poco_path_dic_list = json.loads(poco_path_dic_str) if 'error' in poco_path_dic_list.keys(): print("前端获取节点信息时报错") raise NotPocoServeException(poco_path_dic_list["error"]) else: poco_path_dic = self.get_ui_tree(poco_path_dic_list["result"], {}) self.close() end_time = time.time() print("获取节点数据用时" + str(end_time - start_time)) return poco_path_dic else: return {}
class MakePocoDic: def __init__(self, game_name, phone_id): self.phone_id = phone_id self.game_name = game_name self.thread_file_name = str(threading.get_ident()) self.info = Information() path, name = script_dir_name(__file__) self.logpath = path + "/" + name[:-3] + "/" + name self.complete_poco_path = None self.gpd = GetPocoDic(game_name, phone_id) self.poco_dic = None self.phone_size_list = None self.sleep_time = None def set_phone_size(self): self.phone_size_list = self.gpd.get_phone_size() if self.phone_size_list['orientation'] == 0 or self.phone_size_list[ 'orientation'] == 2: self.phone_size_list = [ self.phone_size_list["width"], self.phone_size_list["height"] ] # 竖屏[x,y] else: self.phone_size_list = [ self.phone_size_list["height"], self.phone_size_list["width"] ] # 横屏[x,y] # def set_poco(self, poco): # self.poco = poco # self.phone_size_list = self.poco.get_screen_size() # # self.info.set_config("Phone_Size", self.thread_file_name, str(self.phone_size_list)) # self.renovate_and_get_poco_dic() # def renovate_and_get_poco_dic(self): # 废弃 # """ # 基础方法 # 根据线程ID,刷新并读取对应本地的poco_dic,调用此方法前需要先调用set_poco() # 目前只适配poco—lua ,安卓、unity后续添加 # :return: poco_dic # """ # try: # self.poco("未命名").exists() # except Exception:#后续可能需要优化,修改整体框架 # self.poco = StdPoco() # self.poco("未命名").exists() # file_path = "D:\\poco_list_file\\" + self.thread_file_name + ".txt" # with open(file_path) as f: # poco_name_dic_str_list = f.readlines() # self.poco_dic = eval(poco_name_dic_str_list[0]) # print("本地poco数据刷新完成") # return self.poco_dic # time.sleep(1) # self.poco_dic = self.gpd.get_poco_dic() # return self.poco_dic def get_poco_dic(self): # file_path = "D:\\poco_list_file\\" + self.thread_file_name + ".txt" # with open(file_path) as f: # poco_name_dic_str_list = f.readlines() # self.poco_dic = eval(poco_name_dic_str_list[0]) # print("本地poco数据获取完成") # return self.poco_dic # g = GetPocoDic()# 新规则,还差指定手机名称 # self.poco_dic = g.get_poco_dic() time.sleep(0.8) self.poco_dic = self.gpd.get_poco_dic() if self.poco_dic == {}: print("未获得任何节点信息") return self.poco_dic def get_poco_name_path_list(self): self.get_poco_dic() print("获取列表,刷新poco") poco_name_path_list = self.poco_dic.keys() return poco_name_path_list def is_this_text(self, poco_path, text, is_in=False): """ 判断节点的文字 :param poco_path: 节点的绝对路径 :param text: 需要判断的文字 :return: bool """ if self.poco_dic == None: # 点击之后会清空,需要刷一下最新的UI树 self.get_poco_dic() if poco_path in self.poco_dic.keys(): print("找到路径" + poco_path) this_text = self.poco_dic[poco_path]["text"] if is_in: if text in this_text: return True else: return False else: if this_text == text: return True else: return False else: print(poco_path + "路径未找到") raise NoneException("对比text属性时," + poco_path) def is_visible(self, poco_path): """ 判断节点能不能点击,一般用作点击之后的状态判断 :param poco_path: :return: """ if self.poco_dic == None: # 点击之后会清空,unexpected会直接调用该方法,所以需要刷一下最新的UI树 self.get_poco_dic() if poco_path in self.poco_dic.keys(): poco_path_pos = self.poco_dic[poco_path]["pos"] poco_path_Visible = self.poco_dic[poco_path]["getVisible"] if poco_path_pos[0] >= 1 or poco_path_pos[1] >= 1: print(poco_path + "-节点坐标在屏幕外,无法点击") return False elif not poco_path_Visible: print(poco_path + "-getVisible属性为False,无法点击") return False else: print(poco_path + "-节点坐标在屏幕内,getVisible属性为True,可以点击") return True # 看看某些唯一路径存不存在简写 for dic_key in self.poco_dic.keys(): if poco_path in dic_key: # 是简写路径的话就把完整路径传递出去 poco_path_pos = self.poco_dic[dic_key]["pos"] poco_path_Visible = self.poco_dic[dic_key]["getVisible"] if poco_path_pos[0] >= 1 or poco_path_pos[1] >= 1: print(dic_key + "-节点坐标在屏幕外,当做不存在") return False elif not poco_path_Visible: print(poco_path + "-getVisible属性为False,无法点击") return False else: print(poco_path + "-节点坐标在屏幕内,getVisible属性为True,可以点击") return True return False def is_in_dic(self, poco_path, poco_dic_input=None): if poco_dic_input != None: self.poco_dic = poco_dic_input if self.poco_dic == None: # 点击之后会清空,unexpected会直接调用该方法,所以需要刷一下最新的UI树 self.get_poco_dic() if poco_path in self.poco_dic.keys(): poco_path_pos = self.poco_dic[poco_path]["pos"] if poco_path_pos[0] >= 1 or poco_path_pos[1] >= 1: print(poco_path + "-节点坐标在屏幕外,当做不存在") return False else: print("找到路径" + poco_path) self.complete_poco_path = poco_path return True # 看看某些唯一路径存不存在简写 for dic_key in self.poco_dic.keys(): if poco_path in dic_key: # 是简写路径的话就把完整路径传递出去 poco_path_pos = self.poco_dic[dic_key]["pos"] if poco_path_pos[0] >= 1 or poco_path_pos[1] >= 1: print(dic_key + "-节点坐标在屏幕外,当做不存在") return False else: print("找到路径" + dic_key) self.complete_poco_path = dic_key return True return False def get_poco_pos(self, poco_path, click_list=None): # todo 闪退检测机制需要优化 """ 基础方法 识别手机分辨率,换算ui控件的坐标 :param poco_path: :return:[x, y] """ self.game_is_die() if self.phone_size_list == None: self.set_phone_size() if self.poco_dic == None: self.get_poco_dic() for i in range(5): if self.is_in_dic(poco_path): print("待点击路径:" + self.complete_poco_path) # 这里获取的是锚点坐标,当anchorPoint属性为[0,0]时,也是点击的左上角,需要做一个转换,直接点击中心点 pos_list = self.poco_dic[self.complete_poco_path]['pos'] pos_anchorPoint = self.poco_dic[ self.complete_poco_path]['anchorPoint'] pos_size = self.poco_dic[self.complete_poco_path]['size'] # [宽,高] # phone_list_str = self.info.get_config("Phone_Size", self.thread_file_name) # phone_list = eval(phone_list_str) # 取整 if pos_anchorPoint[0] == 0.5 and pos_anchorPoint[1] == 0.5: pos_x = pos_list[0] pos_y = pos_list[1] elif pos_anchorPoint[0] == 0 and pos_anchorPoint[1] == 0: pos_x = pos_list[0] + pos_size[0] * 0.5 pos_y = pos_list[1] + pos_size[1] * 0.5 elif pos_anchorPoint[0] == 1 and pos_anchorPoint[1] == 0.5: pos_x = pos_list[0] - pos_size[0] * 0.5 pos_y = pos_list[1] elif pos_anchorPoint[0] == 0.5 and pos_anchorPoint[1] == 1: pos_x = pos_list[0] pos_y = pos_list[1] - pos_size[1] * 0.5 elif pos_anchorPoint[0] == 1 and pos_anchorPoint[1] == 1: pos_x = pos_list[0] - pos_size[0] * 0.5 pos_y = pos_list[1] - pos_size[1] * 0.5 elif pos_anchorPoint[0] == 0 and pos_anchorPoint[1] == 0.5: pos_x = pos_list[0] + pos_size[0] * 0.5 pos_y = pos_list[1] else: # [0.5,0] pos_x = pos_list[0] pos_y = pos_list[1] + pos_size[1] * 0.5 # 上面已经把pos_x和pos_y都换算到中心点的pos了 # 正常是直接点击 if click_list == None: x = int(self.phone_size_list[0] * pos_x) y = int(self.phone_size_list[1] * pos_y) else: # 需要偏移量的话就点击这里 shifting_x = click_list[0] - 0.5 shifting_y = click_list[1] - 0.5 pos_x = pos_x + pos_size[0] * shifting_x pos_y = pos_y + pos_size[1] * shifting_y x = int(self.phone_size_list[0] * pos_x) y = int(self.phone_size_list[1] * pos_y) print(poco_path + "pos属性为:" + str(pos_list) + "坐标为:" + str([x, y])) return [x, y] else: time.sleep(3) print("第" + str(i + 1) + "次未找到,再次查找" + poco_path) if i >= 4: # 排除了游戏被切到后台导致找不到的情况 # 切回游戏后,仍然还是找不到 snapshot(msg="poco节点未找到") raise NoneException(self.phone_id + ":" + poco_path) if i >= 1: # 第2次的时候判断一下 # 开始检测是否是游戏不在了 game_activity_str = self.gpd.get_device_adb_shell( "shell dumpsys window | findstr mCurrentFocus" ) # 判断是否是被切到了后台 if self.game_name in game_activity_str: # 没有被切到后台 # 游戏还在,确实找不到 snapshot(msg="poco节点未找到") raise NoneException(poco_path) else: # 切到了后台,或者疑似闪退 game_running_str = self.gpd.get_device_adb_shell( "shell dumpsys activity processes") if self.game_name in game_running_str: snapshot(msg="游戏还在,疑似切到后台") # 可能被切到了后台 # 重新切回到游戏 start_app(self.game_name) time.sleep(3) snapshot(msg="切回游戏完毕") # 拉起后还有一次查找的机会 else: snapshot(msg="游戏不在,疑似闪退") # 可能闪退 time.sleep(2) start_app(self.game_name) time.sleep(5) snapshot(msg="重启完毕") # 游戏闪退就直接终止了 raise GameServerStopException("游戏闪退,终止脚本") self.get_poco_dic() def my_touch(self, poco_path, click_list=None): touch_int_list = self.get_poco_pos(poco_path, click_list) self.touch(touch_int_list) def touch_pos(self, pos_list_int, is_sleep=True): """ 点击方法 :param pos_list_int: 控件坐标,控件的pos属性 :return: """ if self.phone_size_list == None: self.set_phone_size() if self.sleep_time == None: self.sleep_time = int( self.info.get_config("com.youzu.yztest_nosdk", "allphnoe_poco_sleep_time")) poco_path_pos = pos_list_int x = int(poco_path_pos[0] * self.phone_size_list[0]) y = int(poco_path_pos[1] * self.phone_size_list[1]) self.touch([x, y]) if is_sleep: time.sleep(self.sleep_time) else: pass def swipe_pos(self, start_pos_list, end_pos_list, timein): """ 滑动方法 :param start_pos_list: 滑动起始控件坐标 :param end_pos_list: 滑动结束控件坐标 :param duration_input: 滑动过程持续时间 :return: """ if self.phone_size_list == None: self.set_phone_size() if self.sleep_time == None: self.sleep_time = int( self.info.get_config("com.youzu.yztest_nosdk", "allphnoe_poco_sleep_time")) start_x = int(self.phone_size_list[0] * start_pos_list[0]) start_y = int(self.phone_size_list[1] * start_pos_list[1]) end_x = int(self.phone_size_list[0] * end_pos_list[0]) end_y = int(self.phone_size_list[1] * end_pos_list[1]) swipe([start_x, start_y], [end_x, end_y], duration=timein) sleep(self.sleep_time) self.poco_dic = None def touch(self, pos_list): """ 输入坐标,使用airtest框架的touch点击 :param pos_list: [123,123] :return: """ touch(pos_list) print("点击坐标" + str(pos_list) + "完成", ) self.poco_dic = None # def touch_poco_obj(self, poco_path, click_list): # todo 闪退检测机制需要优化 # self.game_is_die() # if self.phone_size_list == None: # self.set_phone_size() # if self.poco_dic == None: # self.get_poco_dic() # for i in range(5): # if self.is_in_dic(poco_path): # shifting_x = click_list[0] - 0.5 # shifting_y = click_list[1] - 0.5 # poco_path_pos = self.poco_dic[self.complete_poco_path]['pos'] # poco_path_size = self.poco_dic[self.complete_poco_path]['size'] # posx = poco_path_pos[0] + poco_path_size[0] * shifting_x # posy = poco_path_pos[1] + poco_path_size[1] * shifting_y # phone_x = self.phone_size_list[0] # phone_y = self.phone_size_list[1] # x = int(posx * phone_x) # y = int(posy * phone_y) # self.touch([x, y]) # break # else: # time.sleep(3) # print("第" + str(i + 1) + "次未找到,再次查找" + poco_path) # if i >= 4: # # 排除了游戏被切到后台导致找不到的情况 # # 切回游戏后,仍然还是找不到 # snapshot(msg="poco节点未找到") # raise NoneException(poco_path) # if i == 1: # 第2次的时候判断一下 # # 开始检测是否是游戏不在了 # if self.game_is_die(): # stop_app(self.game_name) # raise GameServerStopException("游戏闪退,终止脚本") # else: # 游戏没死也没后台,确实找不到 # raise NoneException(poco_path) # self.get_poco_dic() def game_is_die(self): """ :return: """ game_activity_str = self.gpd.get_device_adb_shell( "shell dumpsys window | findstr mCurrentFocus") # 判断是否是被切到了后台 if self.game_name in game_activity_str: # 没有被切到后台 # 游戏还在,确实找不到 # snapshot(msg="游戏还在") return False else: # 切到了后台,或者疑似闪退 game_running_str = self.gpd.get_device_adb_shell( "shell dumpsys activity processes") if self.game_name in game_running_str: snapshot(msg="游戏还在,疑似切到后台") # 可能被切到了后台 # 重新切回到游戏 start_app(self.game_name) time.sleep(3) snapshot(msg="切回游戏完毕") return False # 拉起后还有一次查找的机会 else: snapshot(msg="游戏不在,疑似闪退") # 可能闪退 time.sleep(2) start_app(self.game_name) time.sleep(5) snapshot(msg="重启完毕") # 游戏闪退就直接终止了 return True def my_swipe(self, start_path, end_path, duration=2): start = self.get_poco_pos(start_path) end = self.get_poco_pos(end_path) swipe(start, end, duration=duration) print(str(start) + "滑动至" + str(end) + "完成") self.poco_dic = None return start, end # def get_poco_visible(self, poco_path): # """ # 获取游戏visible属性中的str值 # :param poco_path:poco路径 # :return:True/False # """ # if self.is_in_dic(poco_path): # visible_value_bool = self.poco_dic[self.complete_poco_path]["getVisible"] # return visible_value_bool # else: # snapshot(msg="未找到节点") # raise NoneException # def get_poco_text(self, poco_path): # """ # 获取游戏visible属性中的str值 # :param poco_path:poco对象 # :return:True/False # """ # if self.is_in_dic(poco_path): # visible_value_bool = self.poco_dic[self.complete_poco_path]["text"] # return visible_value_bool # else: # snapshot(msg="未找到节点") # raise NoneException def get_poco_any_value(self, poco_path, value_name_str): """ 获取游戏poco对象value_name_str属性中的值 :param poco_path:poco_path :param value_name_str:属性的key :return: str value """ self.get_poco_dic() if self.is_in_dic(poco_path): visible_value_bool = self.poco_dic[ self.complete_poco_path][value_name_str] if visible_value_bool == "": snapshot(msg=poco_path + "没有" + value_name_str + "属性") raise NoneStrException return visible_value_bool else: snapshot(msg=poco_path + "未找到节点") raise NoneException def get_game_number_instr(self, poco_path): """ 只获取poco对象中text属性中的数字 :param poco_path: :return: """ number_str = self.get_poco_any_value(poco_path, "text") number = filter(str.isdigit, number_str) return int(number.__next__())