扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

pywinauto:Windows桌面应用自动化测试(九)

01/20 07:24
578
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论
上一篇文章地址:pywinauto:Windows桌面应用自动化测试(八)

一、实战常用方法

1、控件数量

上一节我们知道了descendants的用法,那么如何使用该方法获取控件对象的数量呢?可以这样:

len(self.dialog.descendants(class_name="QLineEdit", control_type="Edit"))

获取到过后就可以动态知道当前元素的数量,接着在不同数量情况下去定位该元素,类似这样:

    def set_trigger_interval(self, num):        '''设置曝光时间'''        element_num = len(self.dialog.descendants(class_name="QLineEdit", control_type="Edit"))        if element_num == 2:            self.dialog.child_window(class_name="QLineEdit", control_type="Edit", found_index=0).set_text(num)        elif element_num == 4:            self.dialog.child_window(class_name="QLineEdit", control_type="Edit", found_index=2).set_text(num)        send_keys('{ENTER}')

2、一个奇怪现象

对于浏览系统文件的操作,如下图,会存在一个小问题,那就是输入盘符目录+文件夹会导致pywinauto连接的窗口丢失,必须三级目录以上才能正常工作。排查发现输入盘符目录+文件夹会出现两个一样名称的窗口,于是报错。

pywinauto.findwindows.ElementAmbiguousError: There are 2 elements that match the criteria {'title': 'SMoreVision', 'top_level_only': False, 'parent': <uia_element_info.UIAElementInfo - 'Smore Vision', MainForm, 2435420>, 'backend': 'uia'}

    def choise_folder(self, folder_way):        '''浏览:输入保存路径, 不能是盘符目录+文件目录,必须三级目录以上'''        browse_win = self.study_win.child_window(title='选择保存路径')        # 分割路径        directory, folder = os.path.split(folder_way)        print("Directory:", directory)        print("folder:", folder)        way_element = browse_win.child_window(title_re=".*地址.*", found_index=0)        rectangle = self.element_num_of_copies(way_element, 5 / 6)        click(coords=rectangle)        browse_win.child_window(title_re=".*地址.*", class_name="Edit", found_index=0).type_keys("{BACKSPACE}")        browse_win.child_window(title_re=".*地址.*", class_name="Edit", found_index=0).set_text(directory)        send_keys("{ENTER}")        browse_win.child_window(title="文件夹:", class_name="Edit").set_text(folder)    def cancel_choise_folder(self):        '''浏览:取消保存路径'''        self.study_win.child_window(title="取消", class_name="Button").click_input()    def confirm_choise_folder(self):        '''浏览:确认保存路径'''        self.study_win.child_window(title="选择文件夹").click_input()

3、如何处理同名窗口

有这样一种场景,点击一个控件后,会出现一个窗口,再点击一个控件后,又出现一个弹窗。此时弹窗名和窗口名同名,就会出现“2”这种情况,那么该如何操作这两个窗口呢?

前面我们说过found_index这个参数,以及应用每次都能轮训到最新界面的元素,也就是说只要found_index为0,他至少能找到一个窗口,窗口中的元素也会实时更新,设计如下:

    def get_study_win(self):        '''获取 study_win窗口,需要在操作study_win前调用一次'''        title_list = ['SMoreVision', 'Form']        for i in range(2):            try:                # 获取窗口                self.study_win = self.dialog.child_window(title=title_list[i], found_index=0)  # Form                # self.print_window_info(self.study_win)                break            except:                print(f"获取窗口,第{i+1}次失败!")    def add_trigger_samples(self):        '''添加触发样本'''        element_num = len(self.study_win.descendants(class_name="QPushButton"))  # 窗口中的元素数量        print(element_num)        self.study_win.child_window(class_name="QPushButton", found_index=element_num - 6).click_input()  # 当弹窗未出现时,窗口的found_index为0,此时self.study_win为窗口        time.sleep(5)        self.study_win.child_window(class_name="QPushButton", found_index=1).click_input()  # 当弹窗未出现时,窗口的found_index为1,弹窗为0,,此时self.study_win为弹窗

4、发送组合键的方式

如果我想发送组合键Ctrl + A 可以"^a"。在 pywinautotype_keys 方法中,可以使用特殊符号来发送不同的组合键。以下是 pywinauto 支持的主要组合键符号及其对应按键:

  • ^ : Ctrl
  • % : Alt
  • + : Shift

5、窗口大小获取

可以通过get_show_state方法获取状态,示例如下:

    def get_win_state(self):        '''获取 窗口状态 1为最大化 0为缩小 '''        return self.dialog.get_show_state()

官方源码:

    def get_show_state(self):        """Get the show state and Maximized/minimzed/restored state        Returns values as following        window_visual_state_normal = 0        window_visual_state_maximized = 1        window_visual_state_minimized = 2        """        iface = self.iface_window        ret = iface.CurrentWindowVisualState        return ret

6、右键任务栏应用的窗口获取不到

如下图,很明显该窗口是二级窗口,需要连接应用后再连接窗口,问题在于应用都连接不了。这种情况可以采用不连接应用,直接连接窗口进行操作,方法如下:

        time.sleep(5) # 需要增加适当延时等待窗口出现from pywinauto import Desktopapp = Desktop("uia")win = app.window(title="******* 的跳转列表")

7、AI识别:模板匹配

之前文章我们有讲到有些元素找不到,这种情况没办法定位元素,因此,我们可以采用AI识别的方式,加上pywinauto混合使用,如下识别效果:

代码设计如下:

封装ai识别到AiRecognize类中

    def imread_unicode(self, path, flags=cv2.IMREAD_COLOR):        '''# 中文路径图片读取函数'''        # 使用 np.fromfile 读取文件并转换为 numpy 数组,再用 cv2.imdecode 解码        return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags)    def match_image(self, image, template_image_path):        '''模板匹配图标控件        image: 实际图片,可以为本地图片路径,也可以为Image对象,如通过pywinauto的 self.dialog.capture_as_image() 获取        template_image_path: 为模板图像路径,即期望找到的图案        :return match_coords : 返回模板所在实际图片的坐标系        示例用法:        match_image(r"../data/images/示例实际图片.jpg", r"../data/images/工具列表-编辑图标.jpg")        '''        # 处理实际图像        if isinstance(image, str):            image_path = image  # 实际图像            img_rgb = self.imread_unicode(image_path, cv2.IMREAD_UNCHANGED)  # 读取 RGB 图像,转成numpy数组        else:            img_rgb = np.array(image)        img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)  # 转为灰度图        # 处理模板图像        template = self.imread_unicode(template_image_path, cv2.IMREAD_GRAYSCALE)  # 读取模板图像        w, h = template.shape[::-1]  # 获取模板的宽和高        # 执行模板匹配        res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)        threshold = 0.8        loc = np.where(res >= threshold)        # 获取匹配到的图标坐标系(左上角和右下角坐标)        match_coords = []  # 用于存储匹配到的坐标        for pt in zip(*loc[::-1]):            top_left = pt  # 左上角坐标            bottom_right = (pt[0] + w, pt[1] + h)  # 右下角坐标            match_coords.append((top_left, bottom_right))  # 将坐标添加到列表中            # 可视化:在匹配到的位置画矩形            cv2.rectangle(img_rgb, top_left, bottom_right, (0, 0, 255), 2)        # 保存带有匹配结果的图像        output_path = r'../data/images/匹配结果.jpg'        cv2.imencode('.jpg', img_rgb)[1].tofile(output_path)        print('找到匹配图案:', match_coords)        # 检查当前坐标是否与已有的匹配点接近        filtered_coords = []        filtered_threshold = 5        for coord in match_coords:            is_similar = False            for f_coord in filtered_coords:                # 检查当前坐标是否与已有的匹配点接近                if abs(coord[0][0] - f_coord[0][0]) < filtered_threshold and abs(coord[0][1] - f_coord[0][1]) < filtered_threshold:                    is_similar = True                    break            if not is_similar:                filtered_coords.append(coord)        print('过滤匹配图案:', filtered_coords)        # 返回过滤后匹配到的坐标列表        return filtered_coords

使用ai识别pywinauto元素对象

    def element_num_of_copies_from_ai(self, element, template_image_path, res_x=0, fraction=0.5):        '''通用方法:通过AI识别图标的方式获取其坐标        element: pywinauto的元素对象        template_image_path: AiRecognize.match_image方法需要的对象        res_x: 选择匹配结果的第x个结果,默认第一个        fraction: 选择图标元素中间位置        '''        # 处理实际图片        rectangle = element.rectangle()        # 元素的左上角和右下角坐标        L, T, R, B = rectangle.left, rectangle.top, rectangle.right, rectangle.bottom        print('实际图片绝对坐标:', L, T, R, B)        image = element.capture_as_image()  # 获取实际图片        # 处理模板图在实际图片的位置        match_list = AiRecognize().match_image(image, template_image_path)        # 图案的相对坐标 (相对于元素)        tL, tT, tR, tB = match_list[res_x][0] + match_list[res_x][1]  # 获取图标的左上角和右下角坐标        t_width = tR - tL        t_height = tB - tT        tx = int(tL + t_width * fraction)        ty = int(tT + t_height * fraction)        print('模板图案中心点的相对坐标:', tx, ty)        print('模板图案中心点的绝对坐标:', L + tx, T + ty)        # click(coords=(L + tx, T + ty))        return (L + tx, T + ty)

8、AI识别:OCR

同理,之前我们说到的ocr识别,现在将ocr识别封装到AiRecognize 中:

    def ocr_reader(self, image):        '''读取图片中的文字        可以为本地图片路径,也可以为Image对象,如通过pywinauto的 self.dialog.capture_as_image() 获取        '''        # 处理实际图像        if isinstance(image, str):            image_path = image  # 实际图像            image_array = self.imread_unicode(image_path, cv2.IMREAD_UNCHANGED)  # 读取 RGB 图像,转成numpy数组        else:            image_array = np.array(image)        ocr = PaddleOCR(use_angle_cls=True, lang="ch")        result = ocr.ocr(image_array, cls=True)        res = []        print("图片中的文本内容:")        for block in result:            for line in block:                text = line[1][0]  # 提取文本内容                print(text)                res.append(line[1][0])        return res

相关推荐