另一个自动化测试软件的例子:控制modbus poll程序

这个是自动录入modbus poll的程序。


import time
import unittest
import os
import sys

from appium import webdriver

# 导入文件工具类
from file_utils import file_utils

# 使用新的文件工具类替代旧的ConfigManager
class ConfigManager(object):
    """INI配置文件管理器(兼容旧接口)"""

    def __init__(self, config_file='config.ini'):
        """初始化配置管理器"""
        self.config_file = config_file
        self.create_default_config_if_needed()

    def create_default_config_if_needed(self):
        """如果需要则创建默认配置文件"""
        if not os.path.exists(self.config_file):
            template_data = {
                'appium': {
                    'server_url': 'http://127.0.0.1:4723',
                    'implicit_wait': '10'
                },
                'modbus': {
                    'exe_path': r'D:\soft\software\Modbus\Modbus Poll\mbpoll.exe',
                    'timeout': '10'
                },
                'test': {
                    'delay_time': '2',
                    'screenshot_path': 'screenshots'
                },
                'element': {
                    'clear_button': '清除',
                    'one_button': '一',
                    'seven_button': '七',
                    'plus_button': '加',
                    'equals_button': '等于',
                    'calculator_results': 'CalculatorResults',
                    '从站地址': '127.0.0.1',
                    '从站端口': '1001',
                    '从站id': '1',
                    '数据类型': '01 Read Coils (0x)',
                    '数据地址': '600',
                    '数据数量': '100',
                    '显示一列': '20'
                }
            }
            file_utils.create_ini_template(self.config_file, template_data)
            print("已创建默认配置文件: %s" % self.config_file)

    def get(self, section, option, fallback=None):
        """获取配置值"""
        return file_utils.read_ini(self.config_file, section, option, fallback)

    def getint(self, section, option, fallback=0):
        """获取整数配置值"""
        return file_utils.read_ini_int(self.config_file, section, option, fallback)

    def getboolean(self, section, option, fallback=False):
        """获取布尔配置值"""
        return file_utils.read_ini_boolean(self.config_file, section, option, fallback)

# 创建全局配置管理器实例
config = ConfigManager()
class SimpleCalculatorTests(unittest.TestCase):

    @classmethod

    def setUpClass(self):
        #set up appium
        desired_caps = {}

        # 从配置文件获取设置
        app_path = config.get('modbus', 'exe_path', r"D:\soft\software\Modbus\Modbus Poll\mbpoll.exe")
        server_url = config.get('appium', 'server_url', 'http://127.0.0.1:4723')

        print("正在启动应用: %s" % app_path)
        print("连接到Appium服务器: %s" % server_url)

        desired_caps["app"] = app_path
        self.driver = webdriver.Remote(
            command_executor=server_url,
            desired_capabilities=desired_caps)

    @classmethod
    def tearDownClass(self):
        self.driver.quit()

    def test_run_sequence(self):
        """按顺序执行test_AddConfigPar和test_ImportTxtConfig"""
        print("\n? 开始顺序执行测试...")

        try:
            # 先执行test_AddConfigPar
            print("\n=== 执行 test_AddConfigPar ===")
            self.Debug_AddConfigPar()
            print("✅ test_AddConfigPar 执行完成")

            # 等待2秒确保配置生效
            time.sleep(2)

            # 再执行test_ImportTxtConfig
            print("\n=== 执行 test_ImportTxtConfig ===")
            self.Debug_ImportTxtConfig()
            print("✅ test_ImportTxtConfig 执行完成")

            print("\n? 所有测试顺序执行完成!")

        except Exception as e:
            print(f"❌ 顺序执行失败: {e}")
            raise

    def getresults(self):
        displaytext = self.driver.find_element_by_accessibility_id("CalculatorResults").text
        # displaytext = displaytext.strip("Display is " )
        displaytext = displaytext.strip("显示为 ")
        displaytext = displaytext.rstrip(' ')
        displaytext = displaytext.lstrip(' ')
        return displaytext

    def switch_to_english_input(self):
        """切换输入法为英文半角模式 - 使用Selenium键盘事件"""
        try:
            from selenium.webdriver.common.action_chains import ActionChains
            from selenium.webdriver.common.keys import Keys
            
            actions = ActionChains(self.driver)
            
            # 方法1: 使用Alt+Shift切换输入法(Windows标准快捷键)
            print("? 尝试切换输入法...")
            actions.key_down(Keys.ALT).send_keys(Keys.SHIFT).key_up(Keys.ALT).perform()
            time.sleep(0.3)
            
            # 方法2: 使用Ctrl+Space切换输入法(备用方案)
            # actions.key_down(Keys.CONTROL).send_keys(Keys.SPACE).key_up(Keys.CONTROL).perform()
            # time.sleep(0.2)
            
            # 确保半角模式:模拟Shift+Space
            actions.key_down(Keys.SHIFT).send_keys(Keys.SPACE).key_up(Keys.SHIFT).perform()
            time.sleep(0.2)
            
            print("✅ 已尝试切换为英文输入法")
            
        except Exception as e:
            print(f"❌ 输入法切换失败: {e}")
            print("⚠️ 请手动确保输入法为英文半角模式")
            print("? 提示:在Windows中按Alt+Shift可切换输入法")

    def Debug_AddConfigPar(self):
        #self.setUpClass()
        # 从配置文件获取元素名称
        delay_time = config.getint('test', 'delay_time', 1)
        slave_addr = config.get('element', '从站地址', '127.0.0.1')
        slave_port = config.get('element', '从站端口', '1001')
        slave_id = config.get('element', '从站id', '1')
        data_type = config.get('element', '数据类型', '01 Read Coils (0x)')
        data_addr = config.get('element', '数据地址', '600')
        data_count = config.get('element', '数据数量', '100')
        display_count = config.get('element', '显示一列', '20')

        print("执行从站-连接:")
        print(f"延时时间为:{delay_time}")
        txtStr =self.driver.find_element_by_name("Connection").text
        print(txtStr)
        self.driver.find_element_by_name("Connection").click()
        self.driver.find_element_by_name("Connect...\tF3").click()
        time.sleep(1)

        # 使用AutomationId操作输入框
        # 方法1: 直接使用accessibility_id (推荐)
        try:
            # 查找"Connection Setup"对话框
            Connection_dialog = self.driver.find_element_by_name("Connection Setup")

            # 确保输入法为英文半角模式
            self.switch_to_english_input()

            # 查找AutomationId为"1113"的组合框(从站地址)
            slave_addr_combo = Connection_dialog.find_element_by_accessibility_id("1113")
            # 清除现有内容
            slave_addr_combo.clear()
            slave_addr_combo.send_keys(slave_addr)
            time.sleep(delay_time)
            # 查找AutomationId为"1114"的输入框(从站端口)
            slave_port_input = Connection_dialog.find_element_by_accessibility_id("1114")
            slave_port_input.clear()
            slave_port_input.send_keys(slave_port)
            time.sleep(delay_time)
            # 点击OK关闭对话框
            ok_button = Connection_dialog.find_element_by_name("OK")
            ok_button.click()
            time.sleep(delay_time)  
            # 查找AutomationId为"1003"的输入框(从站ID)
            # slave_id_input = self.driver.find_element_by_accessibility_id("1003")
            # slave_id_input.clear()
            # slave_id_input.send_keys(slave_id)

        except Exception as e:
            print(f"使用AutomationId失败: {e}")
        # 等待操作完成
        time.sleep(delay_time)
        print("执行从站-读取设置")
        txtStr =self.driver.find_element_by_name("Setup").text
        print(txtStr)
        txtStr =self.driver.find_element_by_name("Setup").click()

        self.driver.find_element_by_name("Read/Write Definition...  F8").click()
        time.sleep(delay_time)

        # 查找"Read/Write Definition"对话框
        try:
            # 方法1: 通过窗口标题查找对话框
            read_write_dialog = self.driver.find_element_by_name("Read/Write Definition")
            print("找到Read/Write Definition对话框")
        except:
            # 方法2: 通过窗口类型查找
            read_write_dialog = self.driver.find_element_by_class_name("#32770")
            print("通过类名找到对话框")

        # 操作ComboBox控件
        try:
            # 方法1: 使用AutomationId直接定位ComboBox
            combo_box = read_write_dialog.find_element_by_accessibility_id("1005")

            # 方法1a: 使用Appium的ComboBox操作
            combo_box.click()  # 先点击展开
            time.sleep(delay_time)  # 增加等待时间确保下拉列表完全展开

            # 查找并选择"01 Read Coils (0x)"选项 - 仅在Read/Write Definition对话框内
            try:
                print("? 在Read/Write Definition对话框内查找ComboBox下拉选项...")

                # 等待下拉列表展开
                time.sleep(0.5+delay_time)

                # 优化后的简洁选择逻辑:展开下拉列表后使用键盘导航选择
                try:
                    from selenium.webdriver.common.by import By

                    # 记录当前值
                    initial_value = combo_box.get_attribute("Value.Value")
                    print(f"当前ComboBox值: {initial_value}")

                    if initial_value and data_type in str(initial_value):
                        print("✅ 已选中目标选项")
                        target_found = True
                    else:
                        print("? 展开下拉列表并使用键盘导航选择...")

                        # 点击ComboBox展开下拉列表
                        combo_box.click()
                        time.sleep(0.8)  # 等待下拉列表完全展开

                        # 方法1: 使用键盘导航选择(↑↓键遍历)
                        try:
                            # 先按Home键回到顶部
                            combo_box.send_keys("\ue011")
                            time.sleep(0.2)

                            # 记录起始值
                            start_value = combo_box.get_attribute("Value.Value")
                            current_value = start_value
                            attempts = 0
                            max_attempts = 20  # 最多遍历20个选项

                            print("? 开始遍历下拉选项...")

                            while attempts < max_attempts:
                                if current_value and data_type in str(current_value):
                                    print(f"✅ 找到并选中目标选项: {current_value}")
                                    target_found = True
                                    break

                                # 按向下箭头键选择下一个选项
                                combo_box.send_keys("\ue015")
                                time.sleep(0.3)  # 等待选项切换

                                # 获取新的当前值
                                new_value = combo_box.get_attribute("Value.Value")

                                # 如果回到起始值,说明已经遍历完整个列表
                                if new_value == start_value and attempts > 0:
                                    print("⚠️ 已遍历完所有选项,未找到目标")
                                    break

                                current_value = new_value
                                attempts += 1

                                if attempts % 5 == 0:  # 每5次打印一次进度
                                    print(f"  已检查 {attempts} 个选项,当前: {current_value}")

                            # 如果找到了目标选项,按回车确认
                            if target_found:
                                combo_box.send_keys("\ue007")
                                time.sleep(0.3)

                                # 最终验证
                                final_value = combo_box.get_attribute("Value.Value")
                                if final_value and data_type in str(final_value):
                                    print(f"✅ 最终确认选择成功: {final_value}")
                                else:
                                    print(f"⚠️ 选择确认后值不匹配: {final_value}")

                        except Exception as keyboard_error:
                            print(f"键盘导航失败: {keyboard_error}")

                            print("? 尝试直接输入选择...")
                            try:
                                # 清除并输入
                                combo_box.send_keys("\ue009\ue010")  # Ctrl+A全选
                                time.sleep(0.1)
                                combo_box.send_keys("\ue008")  # 退格清除
                                time.sleep(0.2)

                                combo_box.send_keys(data_type)
                                time.sleep(0.5)
                                combo_box.send_keys("\ue007")  # 回车确认

                                time.sleep(0.5)
                                final_value = combo_box.get_attribute("Value.Value")
                                if final_value and data_type in str(final_value):
                                    print(f"✅ 直接输入选择成功: {final_value}")
                                    target_found = True

                            except Exception as input_error:
                                print(f"直接输入失败: {input_error}")

                except Exception as e:
                    print(f"ComboBox选择操作失败: {e}")
            except Exception as e:
                print(f"❌ ComboBox选择失败: {e}")

                # 方法5: 获取对话框内所有可见选项(最终备选)
                try:
                    print("? 获取对话框内所有可见选项...")

                    # 获取对话框内所有可见的ListItem和ComboBox选项
                    all_options = read_write_dialog.find_elements_by_xpath(".//*[@ClassName='ListItem' or contains(@Name, 'Read')]")

                    for option in all_options:
                        try:
                            name = option.get_attribute("Name")
                            print(f"  对话框内选项: {name}")
                            if name and "01 Read Coils (0x)" in str(name):
                                option.click()
                                print("✅ 最终遍历选择成功")
                                break
                        except:
                            continue

                except Exception as e5:
                    print(f"最终备选方法失败: {e5}")

        except Exception as e:
            print(f"ComboBox操作失败: {e}")

        # 继续其他输入操作
        # 确保输入法为英文半角模式
        self.switch_to_english_input()
        
        # 查找AutomationId为"1115"的组合框(从站ID)
        slave_id_combo = read_write_dialog.find_element_by_accessibility_id("1115")
        slave_id_combo.clear()  # 先清除现有内容
        slave_id_combo.send_keys(slave_id)
        print(f"从站ID: {slave_id}")
        time.sleep(delay_time)
        # 查找AutomationId为"1004"的输入框(从站首地址)
        slave_port_input = read_write_dialog.find_element_by_accessibility_id("1004")
        slave_port_input.clear()
        slave_port_input.send_keys(data_addr)
        print(f"从站首地址: {data_addr}")
        time.sleep(delay_time)
        # 查找AutomationId为"1006"的输入框(从站地址数量)
        slave_addr_input = read_write_dialog.find_element_by_accessibility_id("1006")
        slave_addr_input.clear()  # 先清除现有内容
        slave_addr_input.send_keys(data_count)
        print(f"从站地址数量: {data_count}")
        time.sleep(delay_time)

        # 查找AutomationId为"1137"的输入框(从站一行显示的数量)
        # slave_port_input = read_write_dialog.find_element_by_name("20")
        # print(f"从站一行显示的数量: {display_count}")
        slave_port_input = read_write_dialog.find_element_by_name("Fit to Quantity")
        slave_port_input.click()
        time.sleep(delay_time)  
        # 点击OK关闭对话框
        ok_button = read_write_dialog.find_element_by_name("OK")
        ok_button.click()

        # self.driver.find_element_by_name(one_btn).click()
        # self.driver.find_element_by_name(plus_btn).click()
        # self.driver.find_element_by_name(seven_btn).click()
        # self.driver.find_element_by_name(equals_btn).click()

        # result = self.getresults()
        # print("测试结果: %s" % result)
        # self.assertEqual(result, "8")

        # 使用配置文件中的延时
        time.sleep(delay_time)
    def Debug_ImportTxtConfig(self):
        """自动化将数据写入Modbus Poll表格"""
        print("? 开始执行表格数据写入...")
        delay_time = config.getint('test', 'delay_time', 1)
        print(f"延时时间为:{delay_time}")
        try:
            time.sleep(delay_time)
            # 1. 找到表格控件 "Mbpoll1"
            print("? 查找表格控件 Mbpoll1...")
            table = self.driver.find_element_by_name("Mbpoll1")
            print("✅ 找到表格控件")
            time.sleep(delay_time)
            # 2. 获取表格的位置和大小信息
            table_location = table.location
            table_size = table.size
            print(f"? 表格位置: x={table_location['x']}, y={table_location['y']}")
            print(f"? 表格大小: 宽={table_size['width']}, 高={table_size['height']}")

            # 3. 从txt文件读取数据
            try:
                data_file_path = os.path.join(os.path.dirname(__file__), "modbus_data.txt")
                if os.path.exists(data_file_path):
                    with open(data_file_path, 'r', encoding='utf-8') as f:
                        data_content = f.read().strip()
                    print(f"? 从文件读取数据: {len(data_content.split(chr(10)))} 行")
                else:
                    # 使用默认数据
                    data_content = "气缸   1\n气缸   1\n吸嘴   2\n吸嘴   2\n气缸   3\n气缸   3\n吸嘴   4\n吸嘴   4\n气缸   5\n气缸   5"
                    print("⚠️ 未找到modbus_data.txt,使用默认数据")
            except Exception as read_error:
                print(f"⚠️ 读取文件失败: {read_error},使用默认数据")
                data_content = "气缸   1\n气缸   1\n吸嘴   2\n吸嘴   2\n气缸   3"

            # 4. 复制数据到剪贴板
            print("? 复制数据到剪贴板...")
            try:
                import pyperclip
                pyperclip.copy(data_content)
                print("✅ 使用pyperclip复制到剪贴板")
            except ImportError:
                # 备用方案:使用系统剪贴板
                print("⚠️ pyperclip未安装,使用备用剪贴板方法...")
                import subprocess
                # 使用Windows命令行复制到剪贴板
                subprocess.run(['clip'], input=data_content.encode('utf-8'), check=True)
                print("✅ 使用系统剪贴板复制成功")

            # 5. 更智能的表格单元格定位
            from selenium.webdriver.common.action_chains import ActionChains
            actions = ActionChains(SimpleCalculatorTests.driver)

            print(f"\n?️ 测试点击左上角单元格...")
            actions.move_to_element_with_offset(table, 50, 100).click().perform()
            time.sleep(delay_time)

            # 6. 验证并执行粘贴操作
            print("? 验证焦点并执行粘贴...")
            from selenium.webdriver.common.keys import Keys
            
            try:
                # 尝试获取当前焦点元素
                active_element = self.driver.switch_to.active_element

                # 清除现有内容
                active_element.send_keys(Keys.CONTROL + "a")
                time.sleep(0.2)
                active_element.send_keys(Keys.DELETE)
                time.sleep(0.2)

                # 粘贴所有数据
                active_element.send_keys(Keys.CONTROL + "v")
                time.sleep(delay_time)
                
                print("✅ 数据整体粘贴完成")
                
            except Exception as click_error:

                print(f"❌ 第一次点击失败: {click_error}")

        except Exception as e:
            print(f"❌ 表格数据写入失败: {e}")
            # 保存错误截图
            self.driver.save_screenshot("table_write_error.png")
            raise e
        time.sleep(1)
        print("执行从站-读取设置")
        txtStr = self.driver.find_element_by_name("Setup").text
        print(txtStr)
        txtStr = self.driver.find_element_by_name("Setup").click()

        self.driver.find_element_by_name("Read/Write Definition...  F8").click()
        time.sleep(1)
        # 查找"Read/Write Definition"对话框
        try:
            # 方法1: 通过窗口标题查找对话框
            read_write_dialog = self.driver.find_element_by_name("Read/Write Definition")
            print("找到Read/Write Definition对话框")
        except:
            # 方法2: 通过窗口类型查找
            read_write_dialog = self.driver.find_element_by_class_name("#32770")
            print("通过类名找到对话框")
            # 查找AutomationId为"1137"的输入框(从站一行显示的数量)
        display_count = config.get('element', '显示一列', '20')
        slave_port_input = read_write_dialog.find_element_by_name("20")
        print(f"从站一行显示的数量: {display_count}")
        # slave_port_input = read_write_dialog.find_element_by_name("Fit to Quantity")
        slave_port_input.click()
        time.sleep(delay_time)
        # 点击OK关闭对话框
        ok_button = read_write_dialog.find_element_by_name("OK")
        ok_button.click()








if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(SimpleCalculatorTests)
    unittest.TextTestRunner(verbosity=2).run(suite)


下面是config.ini的内容:

[appium]
server_url = http://127.0.0.1:4723
implicit_wait = 10

[modbus]
exe_path = E:\Modbus Tools\Modbus Poll\mbpoll.exe
timeout = 10

[test]
delay_time = 1
screenshot_path = screenshots

[element]
clear_button = 清除
one_button = 一
seven_button = 七
plus_button = 加
equals_button = 等于
calculator_results = CalculatorResults
从站地址 = 127.0.0.1
从站端口 = 502
从站id = 1
数据类型 = 01 Read Coils (0x)
数据地址 = 600
数据数量 = 100
显示一列 = 20


modbus_data.txt是随便准备的要填充的数据:

气缸   1
吸嘴   2
气缸   2
吸嘴   3
气缸   3
吸嘴   4
气缸   4
吸嘴   5
气缸   5
吸嘴   6
气缸   6
吸嘴   7
气缸   7
吸嘴   8
气缸   8
吸嘴   9
气缸   9
吸嘴   10
气缸   10
吸嘴   11
气缸   11
吸嘴   12
气缸   12
吸嘴   13
气缸   13
吸嘴   14
气缸   14
吸嘴   15
气缸   15
吸嘴   16
气缸   16
吸嘴   17
气缸   17
吸嘴   18
气缸   18
吸嘴   19
气缸   19
吸嘴   20
气缸   20
吸嘴   21
气缸   21
吸嘴   22
气缸   22
吸嘴   23
气缸   23
吸嘴   24
气缸   24
吸嘴   25
气缸   25
吸嘴   26
气缸   26
吸嘴   27
气缸   27
吸嘴   28
气缸   28
吸嘴   29
气缸   29
吸嘴   30
气缸   30
吸嘴   31
气缸   31
吸嘴   32
气缸   32
吸嘴   33
气缸   33
吸嘴   34
气缸   34
吸嘴   35
气缸   35
吸嘴   36
气缸   36
吸嘴   37
气缸   37
吸嘴   38
气缸   38
吸嘴   39
气缸   39
吸嘴   40
气缸   40
吸嘴   41
气缸   41
吸嘴   42
气缸   42
吸嘴   43
气缸   43
吸嘴   44
气缸   44
吸嘴   45
气缸   45
吸嘴   46
气缸   46
吸嘴   47
气缸   47
吸嘴   48
气缸   48
吸嘴   49
气缸   49
吸嘴   50
吸嘴   51
气缸   51


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
本帖最后由 勇哥,很想停止 于 2025-08-16 12:09:33 编辑

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2025年9月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864