shelved.patch 154.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 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 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 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 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 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 781 782 783 784 785 786 787 788 789 790 791 792 793 794 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 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
Index: 运维集控/项目测试/运维标准版/cases/08设备管理/01新增设备.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/cases/08设备管理/01新增设备.py b/运维集控/项目测试/运维标准版/cases/08设备管理/01新增设备.py
new file mode 100644
--- /dev/null	(date 1742548002145)
+++ b/运维集控/项目测试/运维标准版/cases/08设备管理/01新增设备.py	(date 1742548002145)
@@ -0,0 +1,108 @@
+import sys
+import os
+
+from hytest.common import SELENIUM_LOG_SCREEN
+
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..')))
+from 运维集控.项目测试.运维标准版.lib.base import *
+
+# 构建 CSV 文件的绝对路径
+csv_path = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', '08设备管理', '新增设备.csv'))
+
+class AreafuntionAdd:
+    tag = ['新增设备']
+    ddt_cases = read_csv_data(csv_path)
+    def teststeps(self):
+        wd = GSTORE['wd']
+
+        #从self.para中解构出数据
+        name = self.name
+        area_group, area_type, area_name, area_ip, remark, info = self.para
+
+        STEP(1, '点击新增按钮')
+        areafuntion_add = WebDriverWait(wd, 10).until(
+            EC.element_to_be_clickable(
+                (By.XPATH, "//div[@class='company-edmit-right']//span[contains(text(),'新增')]"))
+        )
+        areafuntion_add.click()
+        sleep(1)
+
+        STEP(2, f'查找并选择区域分组:{area_group}')
+        if area_group:
+            area_group_input = WebDriverWait(wd, 10).until(
+                EC.presence_of_element_located(
+                    (By.XPATH, "//div[@class='dialog_input']//input[@placeholder='请选择分组']"))
+            )
+            area_group_input.send_keys(area_group)
+            #默认选择第一个分组
+            areagroup_select = WebDriverWait(wd, 10).until(
+                EC.presence_of_element_located(
+                    (
+                    By.XPATH, "//li[@class='el-cascader__suggestion-item']"))
+            )
+            sleep(1)
+            areagroup_select.click()
+        else:
+            print("group_name 为空,不执行选择区域分组的操作")
+
+        STEP(3, f'选择区域类型:{area_type}')
+        if area_type:
+            area_type_input = WebDriverWait(wd, 10).until(
+                EC.presence_of_element_located(
+                    (By.XPATH, "//div[@class='dialog_input']//input[@placeholder='请选择区域类型']"))
+            )
+            area_type_input.send_keys(area_type)
+            #默认选择第一个区域类型
+            areatype_select = WebDriverWait(wd, 10).until(
+                EC.presence_of_element_located(
+                    (By.XPATH, "//div[@x-placement='bottom-start']//ul[@class='el-scrollbar__view el-select-dropdown__list']"))
+            )
+            sleep(1)
+            areatype_select.click()
+        else:
+            print("area_type 为空,不执行选择区域类型的操作")
+
+        STEP(4, f'输入区域名称:{area_name}')
+        area_name_input = WebDriverWait(wd, 10).until(
+            EC.presence_of_element_located(
+                (By.XPATH, "//div[contains(@class,'dialog_input')]//input[contains(@placeholder,'请输入区域名称')]"))
+        )
+        area_name_input.clear()
+        area_name_input.send_keys(area_name)
+
+        STEP(5, f'填写IP地址:{area_ip}')
+        area_ip_input = WebDriverWait(wd, 10).until(
+            EC.presence_of_element_located(
+                (By.XPATH, "//input[@placeholder='IP地址']"))
+        )
+        area_ip_input.clear()
+        area_ip_input.send_keys(area_ip)
+
+        STEP(6, f'填写备注:{remark}')
+        funtion_remark = WebDriverWait(wd, 10).until(
+            EC.presence_of_element_located(
+                (By.XPATH, "//input[@placeholder='备注']"))
+        )
+        funtion_remark.clear()
+        funtion_remark.send_keys(remark)
+
+        STEP(7, '点击确认')
+        commit = WebDriverWait(wd, 10).until(
+            EC.element_to_be_clickable(
+                (By.XPATH, "//div[@aria-label='新增']//span[contains(text(),'确 定')]"))
+        )
+        commit.click()
+
+        STEP(8, '验证是否新增成功')
+        get_menu = WebDriverWait(wd, 10).until(
+            EC.visibility_of_element_located(
+                (By.CSS_SELECTOR, '.el-message__content'))
+        )
+        get_menu1 = get_menu.text
+        CHECK_POINT('检查是否出现成功提示弹窗', get_menu1 == info)
+
+        # 截图并保存
+        SELENIUM_LOG_SCREEN(wd, "50%")
+        sleep(1)
+        wd.refresh()
\ No newline at end of file
Index: 运维集控/项目测试/运维标准版/testdata/07类型标签/新增标签.csv
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/testdata/07类型标签/新增标签.csv b/运维集控/项目测试/运维标准版/testdata/07类型标签/新增标签.csv
--- a/运维集控/项目测试/运维标准版/testdata/07类型标签/新增标签.csv	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/运维集控/项目测试/运维标准版/testdata/07类型标签/新增标签.csv	(date 1742548002209)
@@ -1,0 +1,11 @@
+name,typename,sort
+新增标签-001-标签名称为空,,1
+新增标签-002-排序为空,智慧大屏1,
+新增标签-003-排序为小数,智慧大屏2,1.1
+新增标签-004-排序为负数,智慧大屏3,-1
+新增标签-005-正常新增标签1,智慧屏设备,1
+新增标签-006-正常新增标签2,时序电源,2
+新增标签-007-正常新增标签3,中控主机,3
+新增标签-008-正常新增标签4,音频处理器,4
+新增标签-009-正常新增标签5,视频处理器,5
+新增标签-010-正常新增标签6,网络路由,6
Index: 运维集控/项目测试/运维标准版/lib/base.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>import pandas as pd\r\nimport csv\r\nimport urllib\r\nimport glob\r\nimport subprocess\r\nimport requests\r\nimport json\r\nimport hmac\r\nimport hashlib\r\nimport base64\r\nimport time\r\nimport win32api\r\nimport win32con\r\nimport win32gui\r\nimport logging\r\nfrom hytest import *\r\nimport pandas as pd\r\nfrom selenium import webdriver\r\nfrom datetime import datetime\r\nfrom urllib.parse import urlencode\r\nfrom selenium.webdriver.common.by import By\r\nfrom selenium.webdriver.edge.options import Options\r\nfrom selenium.webdriver.support.wait import WebDriverWait\r\nfrom selenium.webdriver.support import expected_conditions as EC\r\nfrom selenium.common import TimeoutException,ElementNotInteractableException\r\nfrom selenium.webdriver.common.keys import Keys\r\nfrom time import sleep\r\n\r\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\r\n# 打开浏览器,忽略ssh警告\r\ndef open_browser():\r\n    INFO('打开默认浏览器')\r\n    # 更改显示屏分辨率为1920x1080\r\n    # success = change_resolution(1280, 1024)\r\n    success = change_resolution(1920, 1080)\r\n    edge_options = Options()\r\n    edge_options.add_argument('--ignore-certificate-errors')\r\n    edge_options.add_argument('--disable-blink-features=AutomationControlled')\r\n    edge_options.add_argument('--allow-insecure-localhost')\r\n    wd = webdriver.Edge(options=edge_options)\r\n    GSTORE['wd'] = wd\r\n    # wd.get('https://rms.ubainsyun.com/#/login')\r\n    wd.get('https://192.168.5.218:8443/#/login')\r\n    wd.maximize_window()\r\n    wd.implicitly_wait(10)\r\n\r\n# 定义调整屏幕分辨率,仅内部环境跑定时任务需要使用。\r\ndef change_resolution(width, height):\r\n    # 获取当前显示器的设备上下文(Device Context, DC)\r\n    device = win32api.EnumDisplayDevices(None, 0)\r\n    dm = win32api.EnumDisplaySettings(device.DeviceName, win32con.ENUM_CURRENT_SETTINGS)\r\n\r\n    if dm.PelsWidth != width or dm.PelsHeight != height:\r\n        print(f\"Changing resolution to {width}x{height}\")\r\n        dm.PelsWidth = width\r\n        dm.PelsHeight = height\r\n\r\n        # CDS_TEST 是测试模式,如果设置成功则不实际应用更改\r\n        if win32api.ChangeDisplaySettings(dm, win32con.CDS_TEST) != win32con.DISP_CHANGE_SUCCESSFUL :\r\n            print(\"The requested resolution change is not supported.\")\r\n            return False\r\n\r\n        # 实际应用新的分辨率设置\r\n        if win32api.ChangeDisplaySettings(dm, 0) != win32con.DISP_CHANGE_SUCCESSFUL:\r\n            print(\"Failed to change resolution.\")\r\n            return False\r\n\r\n        print(\"Resolution changed successfully.\")\r\n        return True\r\n    else:\r\n        print(\"The requested resolution is already set.\")\r\n        return True\r\n\r\n# 用户进行登录\r\ndef user_login(username, password, captcha):\r\n    wd = GSTORE['wd']\r\n    INFO(f'输入登录账号: {username}')\r\n    username_input = WebDriverWait(wd, 10).until(\r\n        EC.presence_of_element_located((By.XPATH, \"//input[@placeholder='请输入登录账号']\"))\r\n    )\r\n    username_input.clear()\r\n    username_input.send_keys(username)\r\n\r\n    INFO(f'输入登录密码: {password}')\r\n    password_input = WebDriverWait(wd, 10).until(\r\n        EC.presence_of_element_located((By.XPATH, \"//input[@placeholder='请输入登录密码']\"))\r\n    )\r\n    password_input.clear()\r\n    password_input.send_keys(password)\r\n\r\n    INFO(f'输入验证码:{captcha}')\r\n    captcha_input = WebDriverWait(wd, 10).until(\r\n        EC.presence_of_element_located((By.XPATH, \"//input[@placeholder='请输入验证码(区分大小写)']\"))\r\n    )\r\n    captcha_input.clear()\r\n    captcha_input.send_keys(captcha)\r\n\r\n    INFO('点击登录按钮')\r\n    login_button = WebDriverWait(wd, 3).until(\r\n        EC.element_to_be_clickable((By.XPATH, \"//div[@class='loginButton']\"))\r\n    )\r\n    login_button.click()\r\n    sleep(1)\r\n\r\ndef validate_input(username, password, captcha):\r\n    if not isinstance(username, str) or not isinstance(password, str) or not isinstance(captcha, str):\r\n        raise ValueError(\"Invalid input type\")\r\n    if len(username) < 1 or len(password) < 1 or len(captcha) < 1:\r\n        raise ValueError(\"Input cannot be empty\")\r\n\r\ndef run_login_tests(df):\r\n    # 遍历每一行\r\n    for index, row in df.iterrows():\r\n        username = row.get('username', None)\r\n        password = row.get('password', None)\r\n        captcha = row.get('captcha', None)\r\n        if username and password and captcha:\r\n            try:\r\n                validate_input(username, password, captcha)\r\n                user_login(username, password, captcha)\r\n            except ValueError as e:\r\n                print(f\"Invalid input at row {index}: {e}\")\r\n\r\n# 构建当前所在目录\r\ndef main():\r\n    # 获取当前脚本的目录\r\n    current_dir = os.path.dirname(os.path.abspath(__file__))\r\n    csv_file_path = os.path.join(current_dir, '../testdata/01登录模块/登录信息.csv')\r\n    try:\r\n        if not os.path.exists(csv_file_path):\r\n            print(f\"File not found: {csv_file_path}\")\r\n            return\r\n        df = pd.read_csv(csv_file_path, encoding='utf-8')\r\n        run_login_tests(df)\r\n    except Exception as e:\r\n        print(f\"An error occurred: {e}\")\r\n\r\n# 进入后台管理系统页面\r\ndef enter_system():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击系统设置按钮')\r\n    try:\r\n        enter_sys = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'系统设置')]\"))\r\n        )\r\n        enter_sys.click()\r\n        logging.info('系统设置按钮已点击')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking system settings button: {error}\")\r\n\r\n# 进入管理页面\r\ndef enter_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击管理按钮')\r\n    try:\r\n        enter_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//div[@class='el-submenu__title']//span[contains(text(),'管理')]\"))\r\n        )\r\n        enter_mag.click()\r\n        logging.info('管理按钮已点击')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入用户管理页面\r\ndef enter_user_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击用户管理按钮')\r\n    try:\r\n        enter_user_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'用户管理')]\"))\r\n        )\r\n        enter_user_mag.click()\r\n        logging.info('打开用户管理页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入区域分组管理页面\r\ndef enter_areagroup_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击区域分组按钮')\r\n    try:\r\n        enter_areagroup_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'区域分组')]\"))\r\n        )\r\n        enter_areagroup_mag.click()\r\n        logging.info('打开区域分组页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入区域类型页面\r\ndef enter_areatype_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击区域类型按钮')\r\n    try:\r\n        enter_areatype_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'区域类型')]\"))\r\n        )\r\n        enter_areatype_mag.click()\r\n        logging.info('打开区域类型页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入区域功能页面\r\ndef enter_areafuntion_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击区域功能按钮')\r\n    try:\r\n        enter_areafuntion_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'区域功能')]\"))\r\n        )\r\n        enter_areafuntion_mag.click()\r\n        logging.info('打开区域功能页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入区域管理页面\r\ndef enter_area_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击区域管理按钮')\r\n    try:\r\n        enter_area_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'区域管理')]\"))\r\n        )\r\n        enter_area_mag.click()\r\n        logging.info('打开区域管理页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入类型标签页面\r\ndef enter_typetag():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击类型标签按钮')\r\n    try:\r\n        enter_type_tag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'类型标签')]\"))\r\n        )\r\n        enter_type_tag.click()\r\n        logging.info('打开类型标签页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入设备管理页面\r\ndef enter_devices_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击设备管理按钮')\r\n    try:\r\n        enter_devices_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'设备管理')]\"))\r\n        )\r\n        enter_devices_mag.click()\r\n        logging.info('打开设备管理页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入协议管理页面\r\ndef enter_protocol_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击协议管理按钮')\r\n    try:\r\n        enter_protocol_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'协议管理')]\"))\r\n        )\r\n        enter_protocol_mag.click()\r\n        logging.info('打开协议管理页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入数据类型页面\r\ndef enter_datatype():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击数据类型按钮')\r\n    try:\r\n        enter_data_type = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'数据类型')]\"))\r\n        )\r\n        enter_data_type.click()\r\n        logging.info('打开数据类型页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入联动动作页面\r\ndef enter_linkaction():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击联动动作按钮')\r\n    try:\r\n        enter_link_action = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'联动动作')]\"))\r\n        )\r\n        enter_link_action.click()\r\n        logging.info('打开联动动作页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 进入模式管理页面\r\ndef enter_model_manage():\r\n    wd = GSTORE['wd']\r\n    if wd is None:\r\n        raise ValueError(\"WebDriver is not initialized\")\r\n\r\n    logging.info('点击模式管理按钮')\r\n    try:\r\n        enter_model_mag = WebDriverWait(wd, 10).until(\r\n            EC.element_to_be_clickable((By.XPATH, \"//li[contains(text(),'模式管理')]\"))\r\n        )\r\n        enter_model_mag.click()\r\n        logging.info('打开模式管理页面')\r\n    except (TimeoutException, ElementNotInteractableException) as error:\r\n        logging.error(f\"Error clicking manage button: {error}\")\r\n\r\n# 定义钉钉接口函数\r\ndef dingding_send_message(test_report_url, title, mobile, ding_type):\r\n    \"\"\"\r\n    发送钉钉机器人消息\r\n    参考接口文档:https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages#title-7fs-kgs-36x\r\n\r\n    :param test_report_url: 测试报告链接\r\n    :param title: 消息标题\r\n    :param text: 消息内容\r\n    :param mobile: 需要@的手机号列表\r\n    \"\"\"\r\n    # 记录调用此函数的日志\r\n    logging.info(\"开始构建并发送钉钉机器人消息\")\r\n    # 钉钉机器人的 Webhook URL 和密钥(正式环境)\r\n    # webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=b0eea0bbf097ce3badb4c832d2cd0267a50486f395ec8beca6e2042102bb295b'\r\n    # secret = 'SEC928b11659c5fd6476cfa2042edbf56da876abf759289f7e4d3c671fb9a81bf43'\r\n    # 钉钉机器人的 Webhook URL 和密钥(测试环境)\r\n\r\n    if ding_type ==  '标准版巡检':\r\n        webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=7fbf40798cad98b1b5db55ff844ba376b1816e80c5777e6f47ae1d9165dacbb4'\r\n        secret = 'SEC610498ed6261ae2df1d071d0880aaa70abf5e67efe47f75a809c1f2314e0dbd6'\r\n    elif ding_type == '展厅巡检':\r\n        webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=061b6e9b1ae436f356cfda7fe19b6e58e46b62670046a78bd3a4d869118c612d'\r\n        secret = 'SEC93212bd880aad638cc0df2b28a72ef4fdf6651cacb8a6a4bc71dcf09705d458d'\r\n\r\n    # 生成时间戳\r\n    timestamp = str(round(time.time() * 1000))\r\n\r\n    # 生成签名\r\n    secret_enc = secret.encode('utf-8')\r\n    string_to_sign = f'{timestamp}\\n{secret}'\r\n    string_to_sign_enc = string_to_sign.encode('utf-8')\r\n    hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()\r\n    sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))\r\n\r\n    # 构建最终的 Webhook URL\r\n    params = {\r\n        'access_token': webhook_url.split('=')[1],\r\n        'timestamp': timestamp,\r\n        'sign': sign\r\n    }\r\n    encoded_params = urllib.parse.urlencode(params)\r\n    final_webhook_url = f'https://oapi.dingtalk.com/robot/send?{encoded_params}'\r\n\r\n    # 记录最终的 Webhook URL\r\n    logging.info(f\"钉钉机器人Webhook URL: {final_webhook_url}\")\r\n\r\n    # 构建消息体\r\n    headers = {'Content-Type': 'application/json'}\r\n    message = {\r\n        'msgtype': 'link',\r\n        'link': {\r\n            'title': title,\r\n            'messageUrl': test_report_url,\r\n            'text': test_report_url\r\n        },\r\n        \"at\": {\r\n            \"atMobiles\": [mobile],\r\n            \"isAtAll\": True\r\n        }\r\n    }\r\n\r\n    try:\r\n        # 发送 POST 请求\r\n        response = requests.post(final_webhook_url, data=json.dumps(message), headers=headers)\r\n\r\n        # 检查响应状态码\r\n        if response.status_code == 200:\r\n            logging.info('消息发送成功!')\r\n            logging.info(f'响应内容: {response.text}')\r\n        else:\r\n            logging.error(f'消息发送失败,状态码: {response.status_code}')\r\n            logging.error(f'响应内容: {response.text}')\r\n    except requests.exceptions.RequestException as e:\r\n        logging.error(f'请求异常: {e}')\r\n\r\n# 调用框架自带生成的测试报告\r\ndef get_latest_report_file(report_dir, base_url):\r\n    \"\"\"\r\n    获取指定目录下最新的HTML报告文件,并返回带有基础URL的完整路径。\r\n\r\n    :param report_dir: 报告文件所在的目录\r\n    :param base_url: 基础URL\r\n    :return: 最新的HTML报告文件的完整URL,如果没有找到则返回None\r\n    \"\"\"\r\n    # 记录调用此函数的日志\r\n    logging.info(\"开始调用get_latest_report_file函数获取报告文件\")\r\n\r\n    # 确保报告目录存在\r\n    if not os.path.exists(report_dir):\r\n        logging.error(f\"报告目录 {report_dir} 不存在。\")\r\n        return None\r\n\r\n    # 获取指定目录下所有符合模式的HTML报告文件\r\n    report_files = glob.glob(os.path.join(report_dir, 'report_*.html'))\r\n\r\n    # 打印找到的文件列表\r\n    logging.debug(f\"找到的报告文件: {report_files}\")\r\n\r\n    # 如果没有找到报告文件,记录警告信息并返回None\r\n    if not report_files:\r\n        logging.warning(\"在指定目录中没有找到报告文件。\")\r\n        return None\r\n\r\n    # 找到最新修改的报告文件\r\n    latest_file = max(report_files, key=os.path.getmtime)\r\n\r\n    # 获取最新报告文件的最后修改时间\r\n    last_modified_time = datetime.fromtimestamp(os.path.getmtime(latest_file)).strftime('%Y-%m-%d %H:%M:%S')\r\n\r\n    # 记录最新报告文件的信息\r\n    logging.info(f\"最新报告文件: {latest_file}, 最后修改时间: {last_modified_time}\")\r\n\r\n    # 将文件路径转换为相对于基础URL的相对路径\r\n    relative_path = os.path.relpath(latest_file, report_dir)\r\n\r\n    # 生成完整的URL\r\n    full_url = f\"{base_url}/{relative_path}\".replace(\"\\\\\", \"/\")\r\n\r\n    # 返回完整的URL\r\n    return full_url\r\n\r\n# 定义发送钉钉报告函数\r\ndef get_reportfile_send_dingding(report_title, report_url_prefix, ding_type):\r\n    try:\r\n        # 获取报告文件所在的目录\r\n        report_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..','log')\r\n\r\n        # 获取基础URL\r\n        base_url = report_url_prefix\r\n\r\n        # 获取最新的报告文件\r\n        latest_report = get_latest_report_file(report_dir, base_url)\r\n\r\n        # 如果找到了最新的报告文件,则发送报告链接到钉钉\r\n        if latest_report:\r\n            logging.info(f\"最新报告文件URL: {latest_report}\")\r\n\r\n            try:\r\n                # 记录调用钉钉消息通知函数的日志\r\n                logging.info(\"开始调用钉钉消息通知函数\")\r\n\r\n                # 调用钉钉发送消息接口进行推送测试报告链接\r\n                dingding_send_message(latest_report, report_title, \"13724387318\", ding_type)\r\n\r\n                # 记录钉钉消息通知函数调用成功的日志\r\n                logging.info(\"钉钉消息通知函数调用成功\")\r\n            except Exception as e:\r\n                # 记录钉钉消息通知函数调用失败的日志\r\n                logging.error(f\"钉钉消息通知函数调用失败: {e}\")\r\n        else:\r\n            # 记录没有找到报告文件的日志\r\n            logging.warning(\"没有找到报告文件以发送。\")\r\n\r\n    except subprocess.CalledProcessError as e:\r\n        # 处理子进程调用失败的异常\r\n        logging.error(f\"命令执行失败,返回码 {e.returncode}: {e.output}\")\r\n    except OSError as e:\r\n        # 处理操作系统相关的异常\r\n        logging.error(f\"发生操作系统错误: {e}\")\r\n    finally:\r\n        # 无论是否成功,都记录测试结束的日志\r\n        logging.info(\"自动化测试完成。\")\r\n\r\n# 获取csv文件数据\r\ndef read_csv_data(csv_file_path):\r\n    \"\"\"\r\n    读取CSV文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。\r\n    参数:\r\n    csv_file_path (str): CSV文件的路径。\r\n    返回:\r\n    list: 包含字典的列表,每个字典包含测试用例的名称和参数。\r\n    \"\"\"\r\n    # 打开CSV文件,使用只读模式,确保兼容性并处理编码\r\n    with open(csv_file_path, mode='r', newline='', encoding='utf-8') as file:\r\n        # 创建CSV阅读器\r\n        reader = csv.reader(file)\r\n        # 读取表头,为后续数据解析做准备\r\n        headers = next(reader)\r\n        ddt_cases = []\r\n        # 遍历CSV文件中的每一行数据\r\n        for row in reader:\r\n            # 将每一行数据转换为字典,其中包含测试用例的名称和参数\r\n            case = {\r\n                'name': row[0],\r\n                'para': row[1:]\r\n            }\r\n            # 将转换后的测试用例数据添加到列表中\r\n            ddt_cases.append(case)\r\n    # 日志记录:CSV文件已读取\r\n    INFO(\"CSV文件已读取\")\r\n    # 返回包含所有测试用例数据的列表\r\n    return ddt_cases\r\n\r\ndef run_automation_test(report_title, report_url_prefix , ding_type):\r\n    \"\"\"\r\n    运行自动化测试并生成报告。\r\n\r\n    参数:\r\n    - report_title: 报告的标题\r\n    - report_url_prefix: 报告URL的前缀\r\n    - test_case: 测试用例脚本执行的标签名\r\n    \"\"\"\r\n    # 记录测试开始的日志\r\n    logging.info(\"开始自动化测试...\")\r\n\r\n    # 构建运行测试命令\r\n    command = [\r\n        'hytest',\r\n        '--report_title', report_title,\r\n        '--report_url_prefix', report_url_prefix\r\n    ]\r\n\r\n    # 记录将要执行的命令日志\r\n    logging.info(f\"执行命令: {' '.join(command)}\")\r\n\r\n    try:\r\n        # 执行测试命令并获取结果\r\n        result = subprocess.run(command, capture_output=True, text=True, check=True)\r\n\r\n        # 记录命令的标准输出和错误输出\r\n        logging.debug(f\"命令标准输出: {result.stdout}\")\r\n        logging.debug(f\"命令错误输出: {result.stderr}\")\r\n    except subprocess.CalledProcessError as e:\r\n        # 处理子进程调用失败的异常\r\n        logging.error(f\"命令执行失败,返回码 {e.returncode}: {e.output}\")\r\n    except OSError as e:\r\n        # 处理操作系统相关的异常\r\n        logging.error(f\"发生操作系统错误: {e}\")\r\n    finally:\r\n        # 无论测试是否成功,都记录测试结束的日志\r\n        logging.info(\"自动化测试完成。\")\r\n\r\n        # 调用回调函数处理后续操作\r\n        get_reportfile_send_dingding(f\"{report_title}\", report_url_prefix, ding_type)\r\n
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/lib/base.py b/运维集控/项目测试/运维标准版/lib/base.py
--- a/运维集控/项目测试/运维标准版/lib/base.py	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/运维集控/项目测试/运维标准版/lib/base.py	(date 1742548002192)
@@ -1,4 +1,6 @@
+import openpyxl
 import pandas as pd
+import re
 import csv
 import urllib
 import glob
@@ -22,7 +24,7 @@
 from selenium.webdriver.edge.options import Options
 from selenium.webdriver.support.wait import WebDriverWait
 from selenium.webdriver.support import expected_conditions as EC
-from selenium.common import TimeoutException,ElementNotInteractableException
+from selenium.common import TimeoutException, ElementNotInteractableException, NoSuchElementException
 from selenium.webdriver.common.keys import Keys
 from time import sleep
 
@@ -44,7 +46,7 @@
     wd.maximize_window()
     wd.implicitly_wait(10)
 
-# 定义调整屏幕分辨率,仅内部环境跑定时任务需要使用。
+# 定义调整屏幕分辨率,仅虚拟机电脑环境跑定时任务需要使用。
 def change_resolution(width, height):
     # 获取当前显示器的设备上下文(Device Context, DC)
     device = win32api.EnumDisplayDevices(None, 0)
@@ -135,6 +137,19 @@
     except Exception as e:
         print(f"An error occurred: {e}")
 
+def browser_quit():
+    """
+    退出浏览器并释放资源。
+
+    该函数从全局存储中获取浏览器驱动实例,并调用其quit方法来关闭浏览器并释放相关资源。
+    """
+    # 清除浏览器
+    INFO("清除浏览器")
+    # 从全局存储中获取浏览器驱动实例
+    wd = GSTORE['wd']
+    # 调用浏览器驱动实例的quit方法,关闭浏览器并释放资源
+    wd.quit()
+
 # 进入后台管理系统页面
 def enter_system():
     wd = GSTORE['wd']
@@ -390,6 +405,13 @@
     # 记录最终的 Webhook URL
     logging.info(f"钉钉机器人Webhook URL: {final_webhook_url}")
 
+    # 调用测试结果获取函数
+    open_browser()
+    wd = GSTORE['wd']
+    # print(latest_report)
+    test_result = get_test_result(test_report_url, wd)
+    browser_quit()
+
     # 构建消息体
     headers = {'Content-Type': 'application/json'}
     message = {
@@ -397,7 +419,7 @@
         'link': {
             'title': title,
             'messageUrl': test_report_url,
-            'text': test_report_url
+            'text': f"通过:{test_result['pass_percent']}" + f"失败:{test_result['fail_percent']}" + f"异常:{test_result['exception_percent']}",
         },
         "at": {
             "atMobiles": [mobile],
@@ -537,6 +559,7 @@
     # 返回包含所有测试用例数据的列表
     return ddt_cases
 
+# 定义生成的报告,并且发送钉钉通知
 def run_automation_test(report_title, report_url_prefix , ding_type):
     """
     运行自动化测试并生成报告。
@@ -578,3 +601,349 @@
 
         # 调用回调函数处理后续操作
         get_reportfile_send_dingding(f"{report_title}", report_url_prefix, ding_type)
+
+# 获取报告中的通过率、失败率和异常率,用于发送钉钉报告
+def get_test_result(latest_report, wd):
+    """
+    获取测试结果页面的通过率、失败率和异常率
+
+    :param latest_report: 测试结果页面的URL
+    :param wd: WebDriver实例,用于访问和操作网页
+    :return: 包含通过率、失败率和异常率的字典
+    """
+    # 初始化测试结果字典
+    test_result = {
+        "pass_percent": "",
+        "fail_percent": "",
+        "exception_percent": ""
+    }
+
+    # 访问测试结果页面
+    wd.get(latest_report)
+    sleep(5)
+
+    # 点击简略显示
+    safe_click((By.XPATH,"//div[@id='display_mode']"), wd)
+    sleep(5)
+
+    # 定义一个函数来获取和解析百分比
+    def get_percentage(selector, wd):
+        text = elment_get_text(selector, wd)
+        logging.info(f"获取的文本:{text}")
+        match = re.search(r'(\d+(\.\d+)?)%', text)
+        if match:
+            return match.group(0)
+        else:
+            logging.error(f"未找到百分比匹配项,文本内容: {text}")
+            return "0"
+
+    # 获取通过率
+    pass_percent = get_percentage((By.CSS_SELECTOR, "div[class='result_barchart'] div:nth-child(1) span:nth-child(1)"), wd)
+    test_result["pass_percent"] = pass_percent
+
+    # 获取失败率
+    fail_percent = get_percentage(
+        (By.CSS_SELECTOR, "body > div.main_section > div.result > div > div:nth-child(2) > span"), wd)
+    test_result["fail_percent"] = fail_percent
+
+    # 获取异常率
+    exception_percent = get_percentage(
+        (By.CSS_SELECTOR, "body > div.main_section > div.result > div > div:nth-child(3) > span"), wd)
+    test_result["exception_percent"] = exception_percent
+    # 输出test_result
+    logging.info(test_result)
+    print(test_result)
+    sleep(5)
+
+    # 返回测试结果字典
+    return test_result
+
+# 通用方法-点击元素
+def safe_click(element_locator, wd):
+    """
+    对其定位器指定的元素执行安全单击。
+    此函数尝试以处理潜在异常的方式单击元素。
+    它等待元素可见并可单击,然后再尝试单击它。
+    如果该元素在20秒内无法点击,或者它不存在,
+    或者不可交互,它会捕获相应的异常并记录一条信息性消息。
+    参数:
+    -element_locator:要单击的元素的定位器,指定为元组。
+    -wd:用于查找元素并与之交互的WebDriver实例。
+    """
+    try:
+        # Wait up to 20 seconds for the element to be visible
+        element = WebDriverWait(wd, 60).until(EC.visibility_of_element_located(element_locator))
+        # Attempt to click the element
+        element.click()
+    except TimeoutException:
+        # Log a message if the element is not found or not clickable within 20 seconds
+        INFO(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
+    except NoSuchElementException:
+        # Log a message if the element is not found
+        INFO(f"NoSuchElementException: Element {element_locator} not found.")
+    except ElementNotInteractableException:
+        # Log a message if the element is not interactable
+        INFO(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
+
+# 通用方法-在输入框输入内容
+def safe_send_keys(element_locator, value, wd):
+    """
+    安全地向网页元素发送键值。
+    该函数尝试在指定时间内找到指定的网页元素,如果找到并且元素可见,将先清除元素内容,然后发送指定的键值。
+    如果在指定时间内未能找到元素或元素不可点击,则捕获相应的异常并打印错误信息。
+    参数:
+    element_locator (tuple): 用于定位网页元素的策略和定位器的元组,例如(By.ID, 'element_id')。
+    value (str): 要发送到元素的键值字符串。
+    wd: WebDriver实例,用于与浏览器交互。
+    异常处理:
+    - TimeoutException: 如果元素在指定时间内未被找到或不可点击。
+    - NoSuchElementException: 如果元素不存在。
+    - ElementNotInteractableException: 如果元素存在但不可交互。
+    """
+    try:
+        # 等待元素在指定时间内可见
+        element = WebDriverWait(wd, 60).until(EC.visibility_of_element_located(element_locator))
+        element.clear()  # 清除元素的当前值
+        element.send_keys(value)  # 向元素发送指定的键值
+    except TimeoutException:
+        # 如果元素在指定时间内未被找到或不可点击,打印超时异常信息
+        INFO(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
+    except NoSuchElementException:
+        # 如果元素不存在,打印相应异常信息
+        INFO(f"NoSuchElementException: Element {element_locator} not found.")
+    except ElementNotInteractableException:
+        # 如果元素不可交互,打印相应异常信息
+        INFO(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
+
+# 通用方法-输入框清除
+def input_clear(element_locator, wd):
+    """
+    清空输入框中的文本。
+
+    该函数通过元素定位器找到指定的输入框,并尝试清空其内容。它使用显式等待来确保元素在尝试清除之前是可见的。
+
+    参数:
+    - element_locator: 用于定位输入框的元素定位器,通常是一个元组,包含定位方法和定位表达式。
+    - wd: WebDriver实例,用于与浏览器交互。
+
+    异常处理:
+    - TimeoutException: 如果在指定时间内元素不可见,则捕获此异常并打印超时异常消息。
+    - NoSuchElementException: 如果找不到指定的元素,则捕获此异常并打印未找到元素的消息。
+    - ElementNotInteractableException: 如果元素不可操作(例如,元素不可见或不可点击),则捕获此异常并打印相应消息。
+    """
+    try:
+        # 等待元素可见,并在可见后清空输入框。
+        input_element = WebDriverWait(wd, 60).until(EC.visibility_of_element_located(element_locator))
+        input_element.clear()
+    except TimeoutException:
+        # 如果元素在20秒内不可见,打印超时异常消息。
+        print(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
+    except NoSuchElementException:
+        # 如果找不到元素,打印未找到元素的消息。
+        print(f"NoSuchElementException: Element {element_locator} not found.")
+    except ElementNotInteractableException:
+        # 如果元素不可操作,打印元素不可操作的消息。
+        print(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
+
+# 通用方法-回车
+def send_keyboard(element_locator, wd):
+    """
+    向指定元素发送键盘事件。
+
+    该函数尝试找到页面上的一个元素,并向其发送RETURN键点击事件。它使用显式等待来确保元素在尝试交互之前是可见的。
+    如果在指定时间内元素不可见、不存在或不可交互,则捕获相应的异常并打印错误消息。
+
+    参数:
+    - element_locator: 用于定位元素的元组,格式为(by, value)。例如,(By.ID, 'element_id')。
+    - wd: WebDriver实例,用于与浏览器进行交互。
+
+    异常处理:
+    - TimeoutException: 如果元素在20秒内不可见,则捕获此异常并打印超时错误消息。
+    - NoSuchElementException: 如果找不到指定的元素,则捕获此异常并打印未找到元素的错误消息。
+    - ElementNotInteractableException: 如果元素不可交互(例如,被遮挡或不可点击),则捕获此异常并打印相应错误消息。
+    """
+    try:
+        # 等待元素可见,并在可见后向其发送RETURN键点击事件。
+        element = WebDriverWait(wd, 60).until(EC.visibility_of_element_located(element_locator))
+        element.send_keys(Keys.RETURN)
+    except TimeoutException:
+        # 如果元素在指定时间内不可见,打印超时错误消息。
+        print(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
+    except NoSuchElementException:
+        # 如果找不到指定的元素,打印未找到元素的错误消息。
+        print(f"NoSuchElementException: Element {element_locator} not found.")
+    except ElementNotInteractableException:
+        # 如果元素不可交互,打印不可交互错误消息。
+        print(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
+
+# 通用方法-获取提示文本
+def get_notify_text(wd,element_locator):
+    """
+    获取通知文本信息。
+
+    该函数通过WebDriver等待特定的元素出现,并截取其文本内容作为通知信息。此外,它还负责在获取通知文本后进行屏幕截图,
+    以便于后续的调试或记录需要。
+
+    参数:
+    wd (WebDriver): 由上层传入的WebDriver对象,用于操作浏览器。
+
+    返回:
+    str: 提取的通知文本信息。如果未能提取到信息或发生异常,则返回None。
+    """
+    try:
+        # 获取提示信息
+        notify_text = WebDriverWait(wd, 60).until(
+            EC.presence_of_element_located(element_locator)
+        ).text
+        # 屏幕截图
+        SELENIUM_LOG_SCREEN(wd,"50%")
+        return notify_text
+    except Exception as e:
+        # 记录异常信息
+        INFO(f"Exception occurred: {e}")
+
+# 通用方法-获取页面中的文本内容
+def elment_get_text(element_locator, wd):
+    """
+    获取页面元素的文本。
+
+    该函数通过显式等待的方式,确保页面元素在20秒内可见并获取其文本。
+    如果在规定时间内元素不可见、不存在或不可交互,则会捕获相应的异常并打印错误信息。
+
+    参数:
+    - element_locator: 用于定位页面元素的元组,通常包含定位方式和定位值。
+    - wd: WebDriver对象,用于与浏览器进行交互。
+
+    返回:
+    - element_text: 页面元素的文本。如果发生异常,则返回None。
+    """
+    try:
+        # 使用WebDriverWait等待页面元素在20秒内可见,并获取其文本。
+        element_text = WebDriverWait(wd, 60).until(EC.visibility_of_element_located(element_locator)).text
+        return element_text
+    except TimeoutException:
+        # 如果超过20秒元素仍未可见,则捕获TimeoutException异常并打印错误信息。
+        print(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
+    except NoSuchElementException:
+        # 如果页面上不存在该元素,则捕获NoSuchElementException异常并打印错误信息。
+        print(f"NoSuchElementException: Element {element_locator} not found.")
+    except ElementNotInteractableException:
+        # 如果元素存在但不可交互(例如被遮挡),则捕获ElementNotInteractableException异常并打印错误信息。
+        print(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
+
+# 通用方法-读取XLSX文件中的数据,并将其转换为一个包含字典的列表
+def read_xlsx_data(xlsx_file_path):
+    """
+    读取XLSX文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。
+
+    参数:
+    xlsx_file_path (str): XLSX文件的路径。
+
+    返回:
+    list: 包含字典的列表,每个字典包含测试用例的名称和参数。
+    """
+    # 打开XLSX文件
+    workbook = openpyxl.load_workbook(xlsx_file_path)
+    # 假设数据在第一个工作表中
+    sheet = workbook.active
+
+    # 读取表头,从第三行开始
+    headers = [cell.value for cell in sheet[3]]
+
+    # 打印表头列名
+    # print(f"表头列名: {headers}")
+
+    # 找到表头中名为 'JSON' 的列索引
+    try:
+        json_index = headers.index('JSON')
+    except ValueError as e:
+        raise ValueError(f"表头中没有找到所需的列: {e}")
+
+    ddt_cases = []
+    # 遍历XLSX文件中的每一行数据,从第四行开始
+    for row in sheet.iter_rows(min_row=4, values_only=True):
+        # 获取 JSON 列的数据
+        json_data = row[json_index]
+
+        # 打印 JSON 数据以进行调试
+        # print(f"JSON 数据: {json_data}")
+
+        # 解析 JSON 字符串
+        try:
+            if json_data:
+                parsed_json = json.loads(json_data)
+            else:
+                raise ValueError("JSON 数据为空")
+        except json.JSONDecodeError:
+            raise ValueError(f"无法解析 JSON 数据: {json_data}")
+
+        # 将解析后的 JSON 数据添加到列表中
+        ddt_cases.append(parsed_json)
+
+    # 日志记录:XLSX文件已读取
+    INFO("XLSX文件已读取")
+    # 返回包含所有测试用例数据的列表
+    return ddt_cases
+
+# 通用方法-转换字符串类型
+def get_by_enum(type_str):
+    """
+    将字符串类型的定位器类型转换为 selenium.webdriver.common.by.By 枚举类型。
+
+    参数:
+    type_str (str): 定位器类型字符串,例如 'XPATH'。
+
+    返回:
+    selenium.webdriver.common.by.By: 对应的 By 枚举类型。
+    """
+    type_str = type_str.upper()
+    if type_str == 'XPATH':
+        return By.XPATH
+    elif type_str == 'ID':
+        return By.ID
+    elif type_str == 'NAME':
+        return By.NAME
+    elif type_str == 'CLASS_NAME':
+        return By.CLASS_NAME
+    elif type_str == 'CSS_SELECTOR':
+        return By.CSS_SELECTOR
+    elif type_str == 'TAG_NAME':
+        return By.TAG_NAME
+    elif type_str == 'LINK_TEXT':
+        return By.LINK_TEXT
+    elif type_str == 'PARTIAL_LINK_TEXT':
+        return By.PARTIAL_LINK_TEXT
+    else:
+        raise ValueError(f"未知的定位器类型: {type_str}")
+
+# 通用方法-用户登录
+def user_login(element_locators, username, password, verifycode):
+    """
+    管理员登录函数。
+    该函数通过模拟用户输入用户名、密码和验证码,并点击登录按钮,以实现管理员登录。
+    """
+    # 获取webdriver实例
+    wd = GSTORE['wd']
+
+    # 打印用户名输入信息
+    INFO(f"输入用户名:{username}")
+    # 向用户名输入框发送用户名
+    safe_send_keys(element_locators['username_locator'], f'{username}', wd)
+    sleep(5)
+
+    # 打印密码输入信息
+    INFO(f"输入密码:{password}")
+    # 向密码输入框发送密码
+    safe_send_keys(element_locators['password_locator'], f"{password}", wd)
+    sleep(5)
+
+    # 打印验证码输入信息
+    INFO(f"输入验证码:{verifycode}")
+    # 向验证码输入验证码
+    safe_send_keys(element_locators['verifycode_locator'], f"{verifycode}", wd)
+    sleep(5)
+
+    #点击登录按钮
+    INFO("点击登录按钮")
+    safe_click(element_locators['submitButton_locator'], wd)
+    sleep(5)
Index: 运维集控/项目测试/运维标准版/testdata/08设备管理/新增设备.csv
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/testdata/08设备管理/新增设备.csv b/运维集控/项目测试/运维标准版/testdata/08设备管理/新增设备.csv
--- a/运维集控/项目测试/运维标准版/testdata/08设备管理/新增设备.csv	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/运维集控/项目测试/运维标准版/testdata/08设备管理/新增设备.csv	(date 1742548002219)
@@ -1,0 +1,2 @@
+name,area_name,device_name,type,brand,model,sn,ip,tag,supplier,contact,price,expiry_time,sort,useYear,description,relateMaster,outMaster
+新增设备-001-正常新增设备,测试区域11,测试设备0,智慧屏设备,华为品牌,Ideahub,123456789,192.168.1.1,资产设备1,华为,13141234567,2000,2025-01-01 12:00:00,1,3,用途说明1,否,否
\ No newline at end of file
Index: 运维集控/项目测试/运维标准版/定时任务执行.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>import schedule\r\nimport threading\r\nimport queue\r\nfrom lib.base import *\r\nimport time\r\nimport logging\r\n\r\n# 配置日志记录器,仅输出到控制台\r\nlogging.basicConfig(\r\n    level=logging.DEBUG,  # 设置日志级别为 DEBUG\r\n    format='%(asctime)s - %(levelname)s - %(message)s',  # 日志格式:时间 - 日志级别 - 消息\r\n    handlers=[\r\n        logging.StreamHandler()  # 将日志输出到控制台\r\n    ]\r\n)\r\n\r\n\"\"\"\r\n    执行指令:\r\n        1.打开一个终端输入:\r\n            - cd .\\运维集控\\项目测试\\运维标准版\\\r\n            - python -m http.server 80 --directory log\r\n        2.打开新终端输入:\r\n            - cd .\\运维集控\\项目测试\\运维标准版\\ngrok\\\r\n            - .\\start.bat\r\n        2.再打开一个终端输入:\r\n            - cd .\\运维集控\\项目测试\\运维标准版\\\r\n            - python .\\定时任务执行.py\r\n\"\"\"\r\n\r\n# 创建一个任务队列,用于存储待处理的任务\r\ntask_queue = queue.Queue()\r\n\r\ndef run_task(task, *args, **kwargs):\r\n    # 将任务及其参数放入任务队列\r\n    task_queue.put((task, args, kwargs))\r\n    logging.debug(f\"任务已加入队列: {task.__name__} with args: {args} and kwargs: {kwargs}\")\r\n\r\ndef worker():\r\n    # 工作线程的主循环\r\n    while True:\r\n        # 从任务队列中获取任务及其参数\r\n        task, args, kwargs = task_queue.get()\r\n        try:\r\n            # 记录任务开始执行的时间\r\n            logging.debug(f\"开始执行任务: {task.__name__} with args: {args} and kwargs: {kwargs}\")\r\n            # 执行任务并获取结果\r\n            result = task(*args, **kwargs)\r\n            # 如果任务有返回结果,记录日志\r\n            if result:\r\n                logging.info(result)\r\n        except Exception as e:\r\n            # 捕获任务执行过程中发生的任何异常并记录错误日志\r\n            logging.error(f\"执行任务时发生错误: {e}\", exc_info=True)\r\n        finally:\r\n            # 无论任务是否成功执行,都标记任务已完成\r\n            task_queue.task_done()\r\n            # 记录任务完成的时间\r\n            logging.debug(f\"任务完成: {task.__name__}\")\r\n\r\ndef start_workers(num_workers):\r\n    # 启动指定数量的工作线程\r\n    for _ in range(num_workers):\r\n        # 创建一个新的工作线程,目标函数为 worker,设置为守护线程\r\n        threading.Thread(target=worker, daemon=True).start()\r\n\r\n# 启动3个工作线程\r\nstart_workers(3)\r\n\r\n# 定义每天定时执行的任务\r\n# 每天早上07:50执行后台系统设置功能测试\r\nfor day in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']:\r\n    schedule.every().__getattribute__(day).at(\"07:50\").do(run_task, run_automation_test, report_title=\"运维系统测试报告\", report_url_prefix=\"http://nat.ubainsyun.com:31135\", ding_type=\"标准版巡检\")\r\n\r\n# 调试使用\r\n#schedule.every().day.at(\"09:59\").do(run_task, run_automation_test, report_title=\"运维系统脚本调试\", report_url_prefix=\"http://nat.ubainsyun.com:31135\",  ding_type=\"标准版巡检\")\r\n\r\ntry:\r\n    # 无限循环,持续检查并执行计划任务\r\n    while True:\r\n        schedule.run_pending()  # 检查并执行所有待处理的任务\r\n        time.sleep(1)  # 每秒检查一次\r\nexcept KeyboardInterrupt:\r\n    # 捕获用户中断信号 (Ctrl+C)\r\n    logging.info(\"Scheduler interrupted by user.\")\r\nexcept Exception as e:\r\n    # 捕获其他未预期的异常\r\n    logging.error(f\"Unexpected error: {e}\", exc_info=True)
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/定时任务执行.py b/运维集控/项目测试/运维标准版/定时任务执行.py
--- a/运维集控/项目测试/运维标准版/定时任务执行.py	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/运维集控/项目测试/运维标准版/定时任务执行.py	(date 1742548002230)
@@ -69,7 +69,7 @@
 # 定义每天定时执行的任务
 # 每天早上07:50执行后台系统设置功能测试
 for day in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']:
-    schedule.every().__getattribute__(day).at("07:50").do(run_task, run_automation_test, report_title="运维系统测试报告", report_url_prefix="http://nat.ubainsyun.com:31135", ding_type="标准版巡检")
+    schedule.every().__getattribute__(day).at("11:21").do(run_task, run_automation_test, report_title="运维系统测试报告", report_url_prefix="http://nat.ubainsyun.com:31135", ding_type="标准版巡检")
 
 # 调试使用
 #schedule.every().day.at("09:59").do(run_task, run_automation_test, report_title="运维系统脚本调试", report_url_prefix="http://nat.ubainsyun.com:31135",  ding_type="标准版巡检")
Index: 统一平台/base/bases.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>import csv\r\nimport glob\r\nimport re\r\nimport urllib\r\nfrom selenium.webdriver.chrome.service import Service\r\nimport json\r\nimport hmac\r\nimport hashlib\r\nimport base64\r\nimport psutil\r\nimport time\r\nimport subprocess\r\nimport logging\r\nfrom hytest import *\r\nfrom selenium import webdriver\r\nfrom selenium.common import ElementNotInteractableException\r\nfrom selenium.webdriver.common.keys import Keys\r\nfrom urllib.parse import urlencode\r\nfrom datetime import datetime\r\nfrom selenium.webdriver.chrome.service import Service as ChromeService\r\nfrom webdriver_manager.chrome import ChromeDriverManager\r\n\r\n# 获取当前脚本的绝对路径\r\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\r\n# 获取当前脚本的父目录\r\nparent_dir = os.path.dirname(current_dir)\r\nlogging.info(parent_dir)\r\n# 添加路径\r\nsys.path.append(current_dir)\r\n\r\n# 配置日志记录器,仅输出到控制台\r\nlogging.basicConfig(\r\n    level=logging.DEBUG,\r\n    format='%(asctime)s - %(levelname)s - %(message)s',\r\n    handlers=[\r\n        logging.StreamHandler()\r\n    ]\r\n)\r\n\r\n# 浏览器初始化函数\r\ndef browser_init(login_type):\r\n    \"\"\"\r\n    初始化浏览器设置和实例。\r\n\r\n    此函数旨在创建并配置一个Chrome浏览器实例,包括设置Chrome选项以排除不必要的日志,\r\n    并尝试打开特定的登录页面。任何初始化过程中出现的错误都会被捕获并记录。\r\n\r\n    参数:\r\n    login_type (str): 指定登录类型,根据不同的登录类型选择不同的URL。\r\n\r\n    返回:\r\n    无\r\n    \"\"\"\r\n    # 标记初始化过程的开始\r\n    INFO(\"'----------' 正在初始化浏览器 '----------'\")\r\n\r\n    # 创建Chrome选项实例,用于配置浏览器行为\r\n    options = webdriver.ChromeOptions()\r\n    # 添加实验性选项,排除某些命令行开关以减少输出日志\r\n    options.add_experimental_option('excludeSwitches', ['enable-Logging'])\r\n    # 忽略证书错误,允许在本地主机上运行时不安全\r\n    options.add_argument('--ignore-certificate-errors')\r\n    # 禁用自动化控制特征检测,避免被网站识别为自动化流量\r\n    options.add_argument('--disable-blink-features=AutomationControlled')\r\n    # 允许不安全的本地主机运行,通常用于开发和测试环境\r\n    options.add_argument('--allow-insecure-localhost')\r\n\r\n    # 使用webdriver_manager自动下载并管理chromedriver\r\n    driver_path = ChromeDriverManager().install()\r\n    service = ChromeService(driver_path)\r\n    # 手动指定ChromeDriver的路径\r\n    # service = Service(r'C:\\Users\\29194\\AppData\\Local\\Programs\\Python\\Python310\\Scripts\\chromedriver.exe')\r\n    try:\r\n        # 创建WebDriver实例\r\n        wd = webdriver.Chrome(service=service, options=options)\r\n        # 设置隐式等待时间为10秒,以允许元素加载\r\n        wd.implicitly_wait(10)\r\n\r\n        # 获取登录URL\r\n        login_url = get_login_url_from_config(login_type)\r\n        # 打开对应类型的登录页面\r\n        wd.get(login_url)\r\n        # 最大化浏览器窗口\r\n        wd.maximize_window()\r\n\r\n        # 将WebDriver实例存储在全局存储器中,以便后续使用\r\n        GSTORE['wd'] = wd\r\n        # 标记初始化过程完成\r\n        INFO(\"'----------' 浏览器初始化完成 '----------'\")\r\n    except Exception as e:\r\n        # 捕获并记录初始化过程中的任何异常\r\n        logging.error(f\"浏览器初始化失败:{e}\")\r\n\r\n# 从配置项config中获取登录URL\r\ndef get_login_url_from_config(login_type):\r\n    \"\"\"\r\n    从配置文件中读取登录URL。\r\n\r\n    参数:\r\n    login_type (str): 指定登录类型,根据不同的登录类型选择不同的URL。\r\n\r\n    返回:\r\n    str: 对应的登录URL。\r\n    \"\"\"\r\n    # 检查 login_type 是否为空或 None\r\n    if not login_type:\r\n        raise ValueError(\"login_type 不能为空\")\r\n\r\n    # 获取当前脚本的绝对路径\r\n    current_dir = os.path.dirname(os.path.abspath(__file__))\r\n    # 构建配置文件的绝对路径,指向 ubains-module-test 目录下的 config.json\r\n    config_path = os.path.abspath(os.path.join(current_dir, '..', 'config', 'config.json'))\r\n    # 规范化路径,防止路径遍历攻击\r\n    config_path = os.path.normpath(config_path)\r\n\r\n    # 记录配置文件路径以便调试,对路径进行脱敏处理\r\n    logging.info(f\"配置文件路径: {os.path.basename(config_path)}\")\r\n\r\n    # 检查文件是否存在\r\n    if not os.path.exists(config_path):\r\n        # 如果配置文件不存在,则抛出异常\r\n        raise FileNotFoundError(f\"配置文件 {config_path} 不存在\")\r\n\r\n    try:\r\n        # 读取配置文件\r\n        with open(config_path, 'r', encoding='utf-8') as config_file:\r\n            # 将配置文件内容解析为 JSON 格式\r\n            config = json.load(config_file)\r\n            # 根据 login_type 获取对应的登录 URL\r\n            login_url = config.get(login_type)\r\n            # 记录正在打开的登录页面类型和 URL\r\n            logging.info(f\"正在打开 {login_type} 的登录页面:{login_url}\")\r\n    except IOError as e:\r\n        # 处理文件读取异常\r\n        raise IOError(f\"读取配置文件失败: {e}\")\r\n    except json.JSONDecodeError as e:\r\n        # 处理 JSON 解析异常\r\n        raise json.JSONDecodeError(f\"解析配置文件失败: {e}\")\r\n\r\n    # 检查是否成功获取到 URL\r\n    if not login_url:\r\n        # 如果未找到对应的 URL,则抛出异常\r\n        raise ValueError(f\"未找到对应的 URL 配置项: {login_type}\")\r\n\r\n    # 返回登录 URL\r\n    return login_url\r\n\r\n# 管理员登录函数\r\ndef admin_login(username, password):\r\n    \"\"\"\r\n    管理员登录函数。\r\n    该函数通过模拟用户输入用户名、密码和验证码,并点击登录按钮,以实现管理员登录。\r\n    \"\"\"\r\n    # 获取webdriver实例\r\n    wd = GSTORE['wd']\r\n\r\n    # 打印用户名输入信息\r\n    INFO(f\"输入用户名:{username}\")\r\n    # 向用户名输入框发送用户名\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入账号或手机号或邮箱号']\"), f'{username}', wd)\r\n\r\n    # 打印密码输入信息\r\n    INFO(f\"输入密码:{password}\")\r\n    # 向密码输入框发送密码\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入密码']\"), f\"{password}\", wd)\r\n\r\n    # 打印验证码输入信息\r\n    INFO(\"输入验证码:csba\")\r\n    # 向验证码输入框发送验证码\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入图形验证码']\"), \"csba\", wd)\r\n    # 隐式等待5秒,以确保验证码被正确处理\r\n    wd.implicitly_wait(5)\r\n\r\n    # 打印登录按钮点击信息\r\n    INFO(\"点击登录按钮\")\r\n    # 点击登录按钮\r\n    safe_click((By.XPATH, \"//input[@value='登 录']\"), wd)\r\n\r\n# 进入预定后台函数\r\ndef enter_the_backend():\r\n    \"\"\"\r\n    进入后台系统界面。\r\n    该函数通过模拟点击操作,找到并点击后台系统入口,以进入后台界面。\r\n    \"\"\"\r\n    # 记录进入后台系统的操作信息\r\n    INFO(\"进入后台\")\r\n\r\n    # 获取webdriver对象,用于后续的页面元素操作\r\n    wd = GSTORE['wd']\r\n\r\n    # 执行点击操作,通过XPath定位到后台系统入口图标并点击\r\n    safe_click((By.XPATH, \"//img[@title='后台系统']\"), wd)\r\n\r\n# 输入框输入值函数\r\ndef safe_send_keys(element_locator, value, wd):\r\n    \"\"\"\r\n    安全地向网页元素发送键值。\r\n    该函数尝试在指定时间内找到指定的网页元素,如果找到并且元素可见,将先清除元素内容,然后发送指定的键值。\r\n    如果在指定时间内未能找到元素或元素不可点击,则捕获相应的异常并打印错误信息。\r\n    参数:\r\n    element_locator (tuple): 用于定位网页元素的策略和定位器的元组,例如(By.ID, 'element_id')。\r\n    value (str): 要发送到元素的键值字符串。\r\n    wd: WebDriver实例,用于与浏览器交互。\r\n    异常处理:\r\n    - TimeoutException: 如果元素在指定时间内未被找到或不可点击。\r\n    - NoSuchElementException: 如果元素不存在。\r\n    - ElementNotInteractableException: 如果元素存在但不可交互。\r\n    \"\"\"\r\n    try:\r\n        # 等待元素在指定时间内可见\r\n        element = WebDriverWait(wd, 5).until(EC.visibility_of_element_located(element_locator))\r\n        element.clear()  # 清除元素的当前值\r\n        element.send_keys(value)  # 向元素发送指定的键值\r\n    except TimeoutException:\r\n        # 如果元素在指定时间内未被找到或不可点击,打印超时异常信息\r\n        INFO(f\"超时异常:元素 {element_locator} 在20秒内未找到或无法点击。\")\r\n    except NoSuchElementException:\r\n        # 如果元素不存在,打印相应异常信息\r\n        INFO(f\"找不到元素:元素 {element_locator} 不存在。\")\r\n    except ElementNotInteractableException:\r\n        # 如果元素不可交互,打印相应异常信息\r\n        INFO(f\"元素不可交互:元素 {element_locator} 当前状态无法操作。\")\r\n\r\n\r\n# 点击按钮函数\r\ndef safe_click(element_locator, wd):\r\n    \"\"\"\r\n    对其定位器指定的元素执行安全单击。\r\n    此函数尝试以处理潜在异常的方式单击元素。\r\n    它等待元素可见并可单击,然后再尝试单击它。\r\n    如果该元素在20秒内无法点击,或者它不存在,\r\n    或者不可交互,它会捕获相应的异常并记录一条信息性消息。\r\n    参数:\r\n    -element_locator:要单击的元素的定位器,指定为元组。\r\n    -wd:用于查找元素并与之交互的WebDriver实例。\r\n    \"\"\"\r\n    try:\r\n        # Wait up to 20 seconds for the element to be visible\r\n        element = WebDriverWait(wd, 5).until(EC.visibility_of_element_located(element_locator))\r\n        # Attempt to click the element\r\n        element.click()\r\n    except TimeoutException:\r\n        # 如果元素在20秒内未找到或不可点击,记录日志\r\n        INFO(f\"超时异常:在20秒内未找到或无法点击元素 {element_locator}。\")\r\n    except NoSuchElementException:\r\n        # 如果找不到指定的元素,记录日志\r\n        INFO(f\"找不到元素:元素 {element_locator} 不存在。\")\r\n    except ElementNotInteractableException:\r\n        # 如果元素不可操作,记录日志\r\n        INFO(f\"元素不可操作:元素 {element_locator} 当前无法交互。\")\r\n\r\n\r\nfrom time import sleep\r\nfrom selenium.webdriver.common.by import By\r\n\r\n# 议题输入和上传议题文件函数\r\ndef issue_send_and_upload(wd, issue_num, issue_name):\r\n    \"\"\"\r\n        输入议题名称以及上传议题文件。\r\n\r\n        参数:\r\n        wd: WebDriver实例,用于操作浏览器。\r\n        issue_num: 需要上传的议题文件数量。\r\n        issue_name: 会议议题的名称。\r\n    \"\"\"\r\n\r\n    # 议题文件的路径列表\r\n    issue_file_path = [\r\n        r\"D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\reports\\issue_file\\5.164Scan 安全报告.pdf\",\r\n        r\"D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\reports\\issue_file\\IdeaTop软件配置&操作说明文档.docx\",\r\n        r\"D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\reports\\issue_file\\ideaTop部署配置视频.mp4\",\r\n        r\"D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\reports\\issue_file\\IdeaTop软件配置&操作说明文档.docx\",\r\n        r\"D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\reports\\issue_file\\议题图片.png\"\r\n    ]\r\n\r\n    # 打印并输入议题名称\r\n    INFO(f\"输入议题名称:{issue_name}\")\r\n    safe_send_keys((By.XPATH, f\"(//input[@placeholder='请输入会议议题'])[1]\"), f\"{issue_name}\", wd)\r\n\r\n    # 点击【上传文件】按钮以开始上传议题文件\r\n    INFO(\"点击【上传文件】按钮\")\r\n    safe_click((By.XPATH, f\"(//div[@class='topicsHandleButton uploadFile'][contains(text(),'上传文件(0)')])[1]\"), wd)\r\n    sleep(2)\r\n\r\n    # 遍历每个议题文件进行上传\r\n    for i in range(issue_num):\r\n        # 检查文件是否存在\r\n        if not os.path.exists(issue_file_path[i]):\r\n            INFO(f\"文件 {issue_file_path[i]} 不存在,跳出函数\")\r\n            return\r\n\r\n        # 定位【选择文件】按钮\r\n        upload_button = wd.find_element(By.XPATH, '//*[@id=\"global-uploader-btn\"]/input')\r\n        sleep(2)\r\n\r\n        # 选择议题文件上传\r\n        upload_button.send_keys(issue_file_path[i])\r\n        # 等待文件上传完成\r\n        sleep(15)\r\n\r\n    # 截取上传完成后的屏幕日志\r\n    SELENIUM_LOG_SCREEN(wd, \"50%\", \"Exhibit_Inspect\", \"Meeting_Message\", \"添加议题文件\")\r\n\r\n    # 点击【确定】按钮完成上传\r\n    safe_click((By.XPATH,\r\n                \"//div[@aria-label='会议文件上传']//div[@class='el-dialog__footer']//div//span[contains(text(),'确定')]\"),\r\n               wd)\r\n    sleep(2)\r\n\r\n# 清除输入框函数\r\ndef input_clear(element_locator, wd):\r\n    \"\"\"\r\n    清空输入框中的文本。\r\n\r\n    该函数通过元素定位器找到指定的输入框,并尝试清空其内容。它使用显式等待来确保元素在尝试清除之前是可见的。\r\n\r\n    参数:\r\n    - element_locator: 用于定位输入框的元素定位器,通常是一个元组,包含定位方法和定位表达式。\r\n    - wd: WebDriver实例,用于与浏览器交互。\r\n\r\n    异常处理:\r\n    - TimeoutException: 如果在指定时间内元素不可见,则捕获此异常并打印超时异常消息。\r\n    - NoSuchElementException: 如果找不到指定的元素,则捕获此异常并打印未找到元素的消息。\r\n    - ElementNotInteractableException: 如果元素不可操作(例如,元素不可见或不可点击),则捕获此异常并打印相应消息。\r\n    \"\"\"\r\n    try:\r\n        # 等待元素可见,并在可见后清空输入框。\r\n        input_element = WebDriverWait(wd, 5).until(EC.visibility_of_element_located(element_locator))\r\n        input_element.clear()\r\n    except TimeoutException:\r\n        # 如果元素在20秒内未找到或不可点击,记录日志\r\n        INFO(f\"超时异常:在20秒内未找到或无法点击元素 {element_locator}。\")\r\n    except NoSuchElementException:\r\n        # 如果找不到指定的元素,记录日志\r\n        INFO(f\"找不到元素:元素 {element_locator} 不存在。\")\r\n    except ElementNotInteractableException:\r\n        # 如果元素不可操作,记录日志\r\n        INFO(f\"元素不可操作:元素 {element_locator} 当前无法交互。\")\r\n\r\n# 键盘输入函数,例如【回车】键等操作\r\ndef send_keyboard(element_locator, wd):\r\n    \"\"\"\r\n    向指定元素发送键盘事件。\r\n\r\n    该函数尝试找到页面上的一个元素,并向其发送RETURN键点击事件。它使用显式等待来确保元素在尝试交互之前是可见的。\r\n    如果在指定时间内元素不可见、不存在或不可交互,则捕获相应的异常并打印错误消息。\r\n\r\n    参数:\r\n    - element_locator: 用于定位元素的元组,格式为(by, value)。例如,(By.ID, 'element_id')。\r\n    - wd: WebDriver实例,用于与浏览器进行交互。\r\n\r\n    异常处理:\r\n    - TimeoutException: 如果元素在20秒内不可见,则捕获此异常并打印超时错误消息。\r\n    - NoSuchElementException: 如果找不到指定的元素,则捕获此异常并打印未找到元素的错误消息。\r\n    - ElementNotInteractableException: 如果元素不可交互(例如,被遮挡或不可点击),则捕获此异常并打印相应错误消息。\r\n    \"\"\"\r\n    try:\r\n        # 等待元素可见,并在可见后向其发送RETURN键点击事件。\r\n        element = WebDriverWait(wd, 5).until(EC.visibility_of_element_located(element_locator))\r\n        element.send_keys(Keys.RETURN)\r\n    except TimeoutException:\r\n        # 如果元素在指定时间内不可见,打印超时错误消息。\r\n        print(f\"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.\")\r\n    except NoSuchElementException:\r\n        # 如果找不到指定的元素,打印未找到元素的错误消息。\r\n        print(f\"NoSuchElementException: Element {element_locator} not found.\")\r\n    except ElementNotInteractableException:\r\n        # 如果元素不可交互,打印不可交互错误消息。\r\n        print(f\"ElementNotInteractableException: Element {element_locator} is not interactable.\")\r\n\r\n# 获取常规提示文本函数,会同步进行截屏操作\r\ndef get_notify_text(wd,element_locator,module_name = None,function_name = None,file_name = None):\r\n    \"\"\"\r\n    获取通知文本信息。\r\n\r\n    该函数通过WebDriver等待特定的元素出现,并截取其文本内容作为通知信息。此外,它还负责在获取通知文本后进行屏幕截图,\r\n    以便于后续的调试或记录需要。\r\n\r\n    参数:\r\n    wd (WebDriver): 由上层传入的WebDriver对象,用于操作浏览器。\r\n    element_locator: 用于定位元素的定位器。\r\n    module_name: 模块名称,用于日志记录。\r\n    function_name: 函数名称,用于日志记录。\r\n    file_name: 屏幕截图的名称,用于日志记录。\r\n\r\n    返回:\r\n    str: 提取的通知文本信息。如果未能提取到信息或发生异常,则返回None。\r\n    \"\"\"\r\n    try:\r\n        # 使用WebDriverWait等待元素出现,并获取其文本内容\r\n        notify_text = WebDriverWait(wd, 5).until(\r\n            EC.presence_of_element_located(element_locator)\r\n        ).text\r\n        if module_name or function_name or file_name:\r\n            # 如果 module_name, function_name, 或 file_name 有值,则使用这些参数进行屏幕截图\r\n            SELENIUM_LOG_SCREEN(wd, \"50%\", module_name, function_name, file_name)\r\n        else:\r\n            # 如果 module_name, function_name, 和 file_name 都没有值,则仅使用 wd 和 \"50%\" 进行屏幕截图\r\n            SELENIUM_LOG_SCREEN(wd, \"50%\")\r\n        return notify_text\r\n    except Exception as e:\r\n        # 当发生异常时,记录异常信息\r\n        INFO(f\"Exception occurred: {e}\")\r\n\r\n# 获取列表的查询结果文本函数\r\ndef elment_get_text(element_locator, wd):\r\n    \"\"\"\r\n    获取页面元素的文本。\r\n\r\n    该函数通过显式等待的方式,确保页面元素在20秒内可见并获取其文本。\r\n    如果在规定时间内元素不可见、不存在或不可交互,则会捕获相应的异常并打印错误信息。\r\n\r\n    参数:\r\n    - element_locator: 用于定位页面元素的元组,通常包含定位方式和定位值。\r\n    - wd: WebDriver对象,用于与浏览器进行交互。\r\n\r\n    返回:\r\n    - element_text: 页面元素的文本。如果发生异常,则返回None。\r\n    \"\"\"\r\n    try:\r\n        # 使用WebDriverWait等待页面元素在20秒内可见,并获取其文本。\r\n        element_text = WebDriverWait(wd, 5).until(EC.visibility_of_element_located(element_locator)).text\r\n        return element_text\r\n    except TimeoutException:\r\n        # 如果超过20秒元素仍未可见,则捕获TimeoutException异常并打印错误信息。\r\n        print(f\"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.\")\r\n    except NoSuchElementException:\r\n        # 如果页面上不存在该元素,则捕获NoSuchElementException异常并打印错误信息。\r\n        print(f\"NoSuchElementException: Element {element_locator} not found.\")\r\n    except ElementNotInteractableException:\r\n        # 如果元素存在但不可交互(例如被遮挡),则捕获ElementNotInteractableException异常并打印错误信息。\r\n        print(f\"ElementNotInteractableException: Element {element_locator} is not interactable.\")\r\n\r\n# 读取csv文件进行数据驱动函数\r\ndef read_csv_data(csv_file_path):\r\n    \"\"\"\r\n    读取CSV文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。\r\n\r\n    参数:\r\n    csv_file_path (str): CSV文件的路径。\r\n\r\n    返回:\r\n    list: 包含字典的列表,每个字典包含测试用例的名称和参数。\r\n    \"\"\"\r\n    # 打开CSV文件,使用只读模式,确保兼容性并处理编码\r\n    with open(csv_file_path, mode='r', newline='', encoding='utf-8') as file:\r\n        # 创建CSV阅读器\r\n        reader = csv.reader(file)\r\n        # 读取表头,为后续数据解析做准备\r\n        headers = next(reader)\r\n        ddt_cases = []\r\n        # 遍历CSV文件中的每一行数据\r\n        for row in reader:\r\n            # 将每一行数据转换为字典,其中包含测试用例的名称和参数\r\n            case = {\r\n                'name': row[0],\r\n                'para': row[1:]\r\n            }\r\n            # 将转换后的测试用例数据添加到列表中\r\n            ddt_cases.append(case)\r\n    # 日志记录:CSV文件已读取\r\n    INFO(\"CSV文件已读取\")\r\n    # 返回包含所有测试用例数据的列表\r\n    return ddt_cases\r\n\r\n# 读取测试用例xlsx文件中的JSON数据进行数据驱动函数\r\nimport openpyxl\r\ndef read_xlsx_data(xlsx_file_path, sheet_name=None):\r\n    \"\"\"\r\n    读取XLSX文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。\r\n\r\n    参数:\r\n    xlsx_file_path (str): XLSX文件的路径。\r\n    sheet_name (str, optional): 工作表的名称。如果未指定,则使用活动工作表。\r\n\r\n    返回:\r\n    list: 包含字典的列表,每个字典包含测试用例的名称和参数。\r\n    \"\"\"\r\n    try:\r\n        # 打开XLSX文件\r\n        workbook = openpyxl.load_workbook(xlsx_file_path)\r\n    except FileNotFoundError:\r\n        raise FileNotFoundError(f\"文件未找到: {xlsx_file_path}\")\r\n    except Exception as e:\r\n        raise Exception(f\"无法打开文件: {e}\")\r\n\r\n    # 选择工作表\r\n    if sheet_name:\r\n        try:\r\n            sheet = workbook[sheet_name]\r\n        except KeyError:\r\n            raise KeyError(f\"工作表未找到: {sheet_name}\")\r\n    else:\r\n        sheet = workbook.active\r\n\r\n    # 读取表头,从第三行开始\r\n    headers = [cell.value for cell in sheet[3]]\r\n\r\n    # 打印表头列名\r\n    # INFO(f\"表头列名: {headers}\")\r\n\r\n    # 找到表头中名为 'JSON' 的列索引\r\n    try:\r\n        json_index = headers.index('JSON')\r\n    except ValueError as e:\r\n        raise ValueError(f\"表头中没有找到所需的列: {e}\")\r\n\r\n    ddt_cases = []\r\n    # 遍历XLSX文件中的每一行数据,从第四行开始\r\n    for row_num, row in enumerate(sheet.iter_rows(min_row=4, values_only=True), start=4):\r\n        # 获取 JSON 列的数据\r\n        json_data = row[json_index]\r\n\r\n        # 打印 JSON 数据以进行调试\r\n        # INFO(f\"行 {row_num} 的 JSON 数据: {json_data}\")\r\n\r\n        # 检查 JSON 数据是否为空\r\n        if json_data is None or json_data.strip() == \"\":\r\n            # INFO(f\"跳过行 {row_num},JSON 数据为空\")\r\n            continue\r\n\r\n        # 解析 JSON 字符串\r\n        try:\r\n            parsed_json = json.loads(json_data)\r\n        except json.JSONDecodeError:\r\n            raise ValueError(f\"行 {row_num} 的 JSON 数据无法解析: {json_data}\")\r\n\r\n        # 将解析后的 JSON 数据添加到列表中\r\n        ddt_cases.append(parsed_json)\r\n\r\n    # 日志记录:XLSX文件已读取\r\n    INFO(\"XLSX文件已读取\")\r\n    # 返回包含所有测试用例数据的列表\r\n    return ddt_cases\r\n\r\n# 获取当前进程的 CPU 占用率函数\r\ndef get_cpu_usage(interval=1):\r\n    \"\"\"\r\n    获取当前进程的 CPU 占用率。\r\n    :param interval: 计算 CPU 使用率的时间间隔(秒)\r\n    :return: 当前进程的 CPU 占用率(百分比)\r\n    \"\"\"\r\n    try:\r\n        process = psutil.Process(os.getpid())\r\n        cpu_usage = process.cpu_percent(interval=interval)\r\n        if isinstance(cpu_usage, (int, float)):\r\n            return cpu_usage\r\n        else:\r\n            logging.error(\"CPU 使用率数据类型不正确\")\r\n            return None\r\n    except psutil.NoSuchProcess:\r\n        logging.error(\"进程不存在\")\r\n        return None\r\n    except psutil.AccessDenied:\r\n        logging.error(\"权限不足\")\r\n        return None\r\n    except Exception as e:\r\n        logging.error(f\"未知错误: {e}\")\r\n        return None\r\n\r\n# 删除目录下的图片文件函数\r\ndef delete_images_in_directory(directory):\r\n    \"\"\"\r\n    删除指定目录下的所有图片文件。\r\n\r\n    该函数会遍历指定的目录,寻找并删除所有扩展名在image_extensions列表中的图片文件。\r\n\r\n    参数:\r\n    directory (str): 图片文件所在的目录路径。\r\n\r\n    返回:\r\n    无返回值。\r\n    \"\"\"\r\n    # 指定要删除的图片文件扩展名\r\n    image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff']\r\n\r\n    # 遍历目录中的所有文件\r\n    for filename in os.listdir(directory):\r\n        # 获取文件的完整路径\r\n        file_path = os.path.join(directory, filename)\r\n\r\n        # 检查文件是否为图片文件\r\n        if any(filename.lower().endswith(ext) for ext in image_extensions):\r\n            try:\r\n                # 删除文件\r\n                os.remove(file_path)\r\n                print(f\"已删除文件: {file_path}\")\r\n            except Exception as e:\r\n                print(f\"删除文件 {file_path} 时出错: {e}\")\r\n\r\n# 判断非法字符函数\r\ndef is_valid_password(password):\r\n    \"\"\"\r\n    验证密码的有效性。\r\n\r\n    有效密码需满足以下条件:\r\n    1. 必须是一个字符串。\r\n    2. 长度至少为11个字符。\r\n    3. 必须包含至少一个小写字母、一个大写字母和一个数字。\r\n    4. 不得包含连续3位相同的字符。\r\n    5. 不得包含连续3位连续的字符(如123, abc)。\r\n\r\n    参数:\r\n    password (str): 待验证的密码。\r\n\r\n    返回:\r\n    bool: 如果密码有效则返回True,否则返回False。\r\n    \"\"\"\r\n    try:\r\n        # 基本类型检查\r\n        if not isinstance(password, str):\r\n            raise ValueError(\"Password must be a string\")\r\n\r\n        # 检查长度,密码至少需要11个字符\r\n        if len(password) < 11:\r\n            return False\r\n\r\n        # 使用正则表达式检查密码是否包含大小写字母和数字\r\n        if not re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{11,}$', password):\r\n            return False\r\n\r\n        # 检查连续3位及以上不重复且不连续组合\r\n        for i in range(len(password) - 2):\r\n            # 检查是否有连续3位相同\r\n            if password[i] == password[i + 1] == password[i + 2]:\r\n                return False\r\n\r\n            # 检查是否有连续3位是连续字符(如123, abc)\r\n            if abs(ord(password[i + 1]) - ord(password[i])) == 1 and abs(ord(password[i + 2]) - ord(password[i + 1])) == 1:\r\n                return False\r\n\r\n        return True\r\n    except Exception as e:\r\n        logging.error(f\"An error occurred: {e}\")\r\n        return False\r\n\r\n# 退出浏览器并释放资源函数\r\ndef browser_quit():\r\n    \"\"\"\r\n    退出浏览器并释放资源。\r\n\r\n    该函数从全局存储中获取浏览器驱动实例,并调用其quit方法来关闭浏览器并释放相关资源。\r\n    \"\"\"\r\n    # 清除浏览器\r\n    INFO(\"清除浏览器\")\r\n    # 从全局存储中获取浏览器驱动实例\r\n    wd = GSTORE['wd']\r\n    # 调用浏览器驱动实例的quit方法,关闭浏览器并释放资源\r\n    wd.quit()\r\n\r\n# 获取最新的HTML报告文件,并拼接网页访问连接函数\r\ndef get_latest_report_file(report_dir, base_url):\r\n    \"\"\"\r\n    获取指定目录下最新的HTML报告文件,并返回带有基础URL的完整路径。\r\n\r\n    :param report_dir: 报告文件所在的目录\r\n    :param base_url: 基础URL\r\n    :return: 最新的HTML报告文件的完整URL,如果没有找到则返回None\r\n    \"\"\"\r\n    # 记录调用此函数的日志\r\n    logging.info(\"开始调用get_latest_report_file函数获取报告文件\")\r\n\r\n    # 确保报告目录存在\r\n    if not os.path.exists(report_dir):\r\n        logging.error(f\"报告目录 {report_dir} 不存在。\")\r\n        return None\r\n\r\n    # 获取指定目录下所有符合模式的HTML报告文件\r\n    report_files = glob.glob(os.path.join(report_dir, 'report_*.html'))\r\n\r\n    # 打印找到的文件列表\r\n    logging.debug(f\"找到的报告文件: {report_files}\")\r\n\r\n    # 如果没有找到报告文件,记录警告信息并返回None\r\n    if not report_files:\r\n        logging.warning(\"在指定目录中没有找到报告文件。\")\r\n        return None\r\n\r\n    # 找到最新修改的报告文件\r\n    latest_file = max(report_files, key=os.path.getmtime)\r\n\r\n    # 获取最新报告文件的最后修改时间\r\n    last_modified_time = datetime.fromtimestamp(os.path.getmtime(latest_file)).strftime('%Y-%m-%d %H:%M:%S')\r\n\r\n    # 记录最新报告文件的信息\r\n    logging.info(f\"最新报告文件: {latest_file}, 最后修改时间: {last_modified_time}\")\r\n\r\n    # 将文件路径转换为相对于基础URL的相对路径\r\n    relative_path = os.path.relpath(latest_file, report_dir)\r\n\r\n    # 生成完整的URL\r\n    full_url = f\"{base_url}/{relative_path}\".replace(\"\\\\\", \"/\")\r\n\r\n    # 返回完整的URL\r\n    return full_url\r\n\r\n# 钉钉群机器人消息发送函数\r\ndef dingding_send_message(latest_report, title, mobile, ding_type):\r\n    \"\"\"\r\n    发送钉钉机器人消息\r\n    参考接口文档:https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages#title-7fs-kgs-36x\r\n\r\n    :param latest_report: 测试报告链接\r\n    :param title: 消息标题\r\n    :param mobile: 需要@的手机号列表\r\n    :param ding_type: 钉钉机器人类型,用于选择不同的 Webhook URL 和密钥\r\n    \"\"\"\r\n    # 记录调用此函数的日志\r\n    logging.info(\"开始构建并发送钉钉机器人消息\")\r\n    # 钉钉机器人的 Webhook URL 和密钥(正式环境)\r\n    # webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=b0eea0bbf097ce3badb4c832d2cd0267a50486f395ec8beca6e2042102bb295b'\r\n    # secret = 'SEC928b11659c5fd6476cfa2042edbf56da876abf759289f7e4d3c671fb9a81bf43'\r\n    # 钉钉机器人的 Webhook URL 和密钥(测试环境)\r\n\r\n    if ding_type ==  '标准版巡检':\r\n        webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=7fbf40798cad98b1b5db55ff844ba376b1816e80c5777e6f47ae1d9165dacbb4'\r\n        secret = 'SEC610498ed6261ae2df1d071d0880aaa70abf5e67efe47f75a809c1f2314e0dbd6'\r\n    elif ding_type == '展厅巡检':\r\n        webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=061b6e9b1ae436f356cfda7fe19b6e58e46b62670046a78bd3a4d869118c612d'\r\n        secret = 'SEC93212bd880aad638cc0df2b28a72ef4fdf6651cacb8a6a4bc71dcf09705d458d'\r\n\r\n    # 生成时间戳\r\n    timestamp = str(round(time.time() * 1000))\r\n\r\n    # 生成签名\r\n    secret_enc = secret.encode('utf-8')\r\n    string_to_sign = f'{timestamp}\\n{secret}'\r\n    string_to_sign_enc = string_to_sign.encode('utf-8')\r\n    hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()\r\n    sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))\r\n\r\n    # 构建最终的 Webhook URL\r\n    params = {\r\n        'access_token': webhook_url.split('=')[1],\r\n        'timestamp': timestamp,\r\n        'sign': sign\r\n    }\r\n    encoded_params = urllib.parse.urlencode(params)\r\n    final_webhook_url = f'https://oapi.dingtalk.com/robot/send?{encoded_params}'\r\n\r\n    # 记录最终的 Webhook URL\r\n    logging.info(f\"钉钉机器人Webhook URL: {final_webhook_url}\")\r\n\r\n    # 调用测试结果获取函数\r\n    browser_init(\"标准版预定系统\")\r\n    wd = GSTORE['wd']\r\n    # print(latest_report)\r\n    test_result = get_test_result(latest_report, wd)\r\n    browser_quit()\r\n\r\n    # 构建消息体\r\n    headers = {'Content-Type': 'application/json'}\r\n    message = {\r\n        'msgtype': 'link',\r\n        'link': {\r\n            'title': title,\r\n            'messageUrl': latest_report,\r\n            'text': f\"通过:{test_result['pass_percent']}\" + f\"失败:{test_result['fail_percent']}\" + f\"异常:{test_result['exception_percent']}\",\r\n        },\r\n        \"at\": {\r\n            \"atMobiles\": [mobile],\r\n            \"isAtAll\": True\r\n        }\r\n    }\r\n\r\n    try:\r\n        # 发送 POST 请求\r\n        response = requests.post(final_webhook_url, data=json.dumps(message), headers=headers)\r\n\r\n        # 检查响应状态码\r\n        if response.status_code == 200:\r\n            logging.info('消息发送成功!')\r\n            logging.info(f'响应内容: {response.text}')\r\n        else:\r\n            logging.error(f'消息发送失败,状态码: {response.status_code}')\r\n            logging.error(f'响应内容: {response.text}')\r\n    except requests.exceptions.RequestException as e:\r\n        logging.error(f'请求异常: {e}')\r\n\r\n# 运行自动化测试函数,并调用获取测试报告链接和钉钉机器人消息发送函数\r\ndef run_automation_test(report_title, report_url_prefix, test_case , ding_type):\r\n    \"\"\"\r\n    运行自动化测试并生成报告。\r\n\r\n    参数:\r\n    - report_title: 报告的标题\r\n    - report_url_prefix: 报告URL的前缀\r\n    - test_case: 测试用例脚本执行的标签名\r\n    - ding_type: 钉钉通知的类型,备用参数,当前代码中未使用\r\n    \"\"\"\r\n    # 记录测试开始的日志\r\n    logging.info(\"开始自动化测试...\")\r\n\r\n    # 构建运行测试命令\r\n    command = [\r\n        'hytest',\r\n        '--report_title', report_title,\r\n        '--report_url_prefix', report_url_prefix,\r\n        '--tag', test_case\r\n    ]\r\n\r\n    # 记录将要执行的命令日志\r\n    logging.info(f\"执行命令: {' '.join(command)}\")\r\n\r\n    try:\r\n        # 执行测试命令并获取结果\r\n        result = subprocess.run(command, capture_output=True, text=True, check=True)\r\n\r\n        # 记录命令的标准输出和错误输出\r\n        logging.debug(f\"命令标准输出: {result.stdout}\")\r\n        logging.debug(f\"命令错误输出: {result.stderr}\")\r\n    except subprocess.CalledProcessError as e:\r\n        # 处理子进程调用失败的异常\r\n        logging.error(f\"命令执行失败,返回码 {e.returncode}: {e.output}\")\r\n    except OSError as e:\r\n        # 处理操作系统相关的异常\r\n        logging.error(f\"发生操作系统错误: {e}\")\r\n    finally:\r\n        # 无论测试是否成功,都记录测试结束的日志\r\n        logging.info(\"自动化测试完成。\")\r\n\r\n        # 调用回调函数处理后续操作\r\n        get_reportfile_send_dingding(f\"{report_title}\", report_url_prefix, ding_type)\r\n\r\n# 定义一个函数,用于获取最新的报告文件,并返回其URL,并调用钉钉消息发送函数\r\ndef get_reportfile_send_dingding(report_title, report_url_prefix, ding_type):\r\n    \"\"\"\r\n    获取最新的报告文件并通过钉钉发送报告链接。\r\n\r\n    参数:\r\n    report_title (str): 报告的标题。\r\n    report_url_prefix (str): 报告URL的前缀。\r\n    ding_type (str): 钉钉消息的类型。\r\n\r\n    返回:\r\n    无\r\n    \"\"\"\r\n    # print(GSTORE['case_pass'])\r\n    try:\r\n        # 获取报告文件所在的目录\r\n        report_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..','reports')\r\n\r\n        # 获取基础URLs\r\n        base_url = report_url_prefix\r\n\r\n        # 获取最新的报告文件\r\n        latest_report = get_latest_report_file(report_dir, base_url)\r\n\r\n        # 如果找到了最新的报告文件,则发送报告链接到钉钉\r\n        if latest_report:\r\n            logging.info(f\"最新报告文件URL: {latest_report}\")\r\n\r\n            try:\r\n                # 记录调用钉钉消息通知函数的日志\r\n                logging.info(\"开始调用钉钉消息通知函数\")\r\n\r\n                # 调用钉钉发送消息接口进行推送测试报告链接\r\n                dingding_send_message(latest_report, report_title, \"13724387318\", ding_type)\r\n\r\n                # 记录钉钉消息通知函数调用成功的日志\r\n                logging.info(\"钉钉消息通知函数调用成功\")\r\n            except Exception as e:\r\n                # 记录钉钉消息通知函数调用失败的日志\r\n                logging.error(f\"钉钉消息通知函数调用失败: {e}\")\r\n        else:\r\n            # 记录没有找到报告文件的日志\r\n            logging.warning(\"没有找到报告文件以发送。\")\r\n\r\n    except subprocess.CalledProcessError as e:\r\n        # 处理子进程调用失败的异常\r\n        logging.error(f\"命令执行失败,返回码 {e.returncode}: {e.output}\")\r\n    except OSError as e:\r\n        # 处理操作系统相关的异常\r\n        logging.error(f\"发生操作系统错误: {e}\")\r\n    finally:\r\n        # 无论是否成功,都记录测试结束的日志\r\n        logging.info(\"自动化测试完成。\")\r\n\r\n\r\nfrom selenium.webdriver.common.action_chains import ActionChains\r\nfrom selenium.webdriver.support.ui import WebDriverWait\r\nfrom selenium.webdriver.support import expected_conditions as EC\r\nfrom selenium.common.exceptions import TimeoutException, NoSuchElementException\r\n# 点击并拖拽函数\r\ndef single_click_and_drag(source_element_locator, target_element_locator, wd):\r\n    \"\"\"\r\n    实现元素从source_element单击后拖拽到target_element的功能\r\n\r\n    :param wd: WebDriver实例\r\n    :param source_element_locator: 拖拽起始元素的定位器\r\n    :param target_element_locator: 拖拽目标元素的定位器\r\n    \"\"\"\r\n    try:\r\n        # 等待页面完全加载\r\n        sleep(3)\r\n\r\n        # 找到源元素和目标元素\r\n        source_element = WebDriverWait(wd, 5).until(EC.element_to_be_clickable(source_element_locator))\r\n        target_element = WebDriverWait(wd, 5).until(EC.element_to_be_clickable(target_element_locator))\r\n\r\n        # 使用ActionChains执行单击并拖拽操作\r\n        actions = ActionChains(wd)\r\n        actions.click_and_hold(source_element)  # 单击并按住源元素\r\n        actions.move_to_element(target_element)  # 移动到目标元素\r\n        actions.release()  # 释放鼠标\r\n        actions.perform()\r\n\r\n        logging.info(\"单击并拖拽操作成功\")\r\n    except TimeoutException as e:\r\n        logging.error(f\"元素查找超时: {e}\")\r\n    except NoSuchElementException as e:\r\n        logging.error(f\"元素未找到: {e}\")\r\n    except Exception as e:\r\n        logging.error(f\"发生未知错误: {e}\")\r\n\r\n# 获取check.txt文件并解析指定信息函数\r\nimport requests\r\nimport os\r\nimport chardet\r\nfrom urllib3.exceptions import InsecureRequestWarning\r\n# 禁用 InsecureRequestWarning 警告\r\nrequests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)\r\ndef fetch_and_parse_check_txt(url, save_path, extract_info):\r\n    \"\"\"\r\n    获取check.txt文件并解析指定信息\r\n\r\n    :param url: check.txt文件的URL\r\n    :param save_path: 文件保存路径\r\n    :param extract_info: 需要提取的信息列表,例如 ['[m]ysql', '[r]edis', '[f]dfs_storaged', '[f]dfs_tracker', '[e]mqx', 'ubains-meeting-api-1.0-SNAPSHOT.jar', 'ubains-meeting-inner-api-1.0-SNAPSHOT.jar', 'uwsgi']\r\n    :return: 提取的信息字典\r\n    \"\"\"\r\n    try:\r\n        # 发送HTTPS请求获取文件内容\r\n        response = requests.get(url, verify=False)  # verify=False 忽略SSL证书验证,生产环境不推荐\r\n        response.raise_for_status()  # 如果响应状态码不是200,抛出异常\r\n\r\n        # 检测文件编码\r\n        detected_encoding = chardet.detect(response.content)['encoding']\r\n        logging.info(f\"检测到的编码: {detected_encoding}\")\r\n\r\n        # 如果检测到的编码为空或不准确,可以手动指定编码\r\n        if not detected_encoding or detected_encoding == 'ascii':\r\n            detected_encoding = 'utf-8'  # 假设文件编码为 utf-8\r\n\r\n        # 将响应内容解码为字符串\r\n        content = response.content.decode(detected_encoding)\r\n\r\n        # 将文件内容保存到指定目录\r\n        with open(save_path, 'w', encoding='utf-8') as file:\r\n            file.write(content)\r\n\r\n        # 解析文件内容\r\n        parsed_info = {}\r\n        for line in content.split('\\n'):\r\n            for info in extract_info:\r\n                if info in line and info not in parsed_info:\r\n                    service_name = info\r\n                    service_status = line.split(info, 1)[1].strip()\r\n                    parsed_info[service_name] = service_status\r\n                    break  # 找到后跳出内层循环,继续处理下一行\r\n\r\n        return parsed_info\r\n\r\n    except requests.exceptions.RequestException as e:\r\n        logging.exception(f\"请求错误: {e}\")\r\n        return None\r\n\r\n# 检查服务状态函数\r\nimport telnetlib\r\ndef check_service_status(host, port):\r\n    \"\"\"\r\n    检查服务状态。\r\n\r\n    通过尝试连接到指定的主机和端口来检查服务是否可用。\r\n\r\n    参数:\r\n    - host: 服务所在的主机地址。\r\n    - port: 服务监听的端口。\r\n\r\n    返回值:\r\n    无\r\n    \"\"\"\r\n    try:\r\n        # 创建Telnet对象并连接到服务器\r\n        tn = telnetlib.Telnet(host, port)\r\n        INFO(f\"成功连接到 {host}:{port}\")\r\n\r\n        # 可以在这里发送命令或读取响应\r\n        # 例如,读取服务器的欢迎信息\r\n        response = tn.read_until(b\"\\n\", timeout=5)\r\n        INFO(f\"服务器响应: {response.decode('ascii')}\")\r\n\r\n        # 关闭连接\r\n        tn.close()\r\n    except Exception as e:\r\n        INFO(f\"连接失败: {e}\")\r\n\r\n# 设置腾讯会议会议号为全局变量函数\r\ndef set_tx_meeting_id(element_locator, wd):\r\n    \"\"\"\r\n    设置腾讯会议ID到全局存储。\r\n\r\n    该函数通过元素定位器获取腾讯会议ID,并将其存储在全局变量GSTORE中,\r\n    以便在其他地方访问和使用该ID。\r\n\r\n    参数:\r\n    - element_locator: 用于定位腾讯会议ID元素的定位器。\r\n    - wd: WebDriver实例,用于与网页交互。\r\n\r\n    返回值:\r\n    无返回值。\r\n    \"\"\"\r\n    # 获取腾讯会议ID文本\r\n    tx_meeting_id = elment_get_text(element_locator, wd)\r\n    # 将腾讯会议ID存储在全局存储中\r\n    GSTORE['tx_meeting_id'] = tx_meeting_id\r\n\r\n# 获取腾讯会议会议号函数\r\ndef get_tx_meeting_id():\r\n    \"\"\"\r\n    获取腾讯会议ID。\r\n\r\n    从全局存储器GSTORE中获取预先存储的腾讯会议ID。这个函数没有输入参数,直接返回存储的会议ID。\r\n\r\n    Returns:\r\n        str: 腾讯会议ID。\r\n    \"\"\"\r\n    return GSTORE.get('tx_meeting_id')\r\n\r\n# 云喇叭设备注册函数\r\ndef voice_device_register(app_id, app_secret, device_sn):\r\n    \"\"\"\r\n    注册语音设备。\r\n\r\n    向指定的API发送POST请求,以注册一个语音设备。需要提供应用的ID和密钥,以及设备的序列号。\r\n\r\n    参数:\r\n    app_id (str): 应用的唯一标识符。\r\n    app_secret (str): 应用的秘密密钥。\r\n    device_sn (str): 设备的序列号。\r\n\r\n    返回:\r\n    无直接返回值,但会记录请求结果到日志。\r\n    \"\"\"\r\n    # 构建请求体,包含注册所需的必要信息\r\n    body = {\r\n        \"app_id\": app_id,\r\n        \"app_secret\": app_secret,\r\n        \"device_sn\": device_sn\r\n    }\r\n\r\n    # 设置请求头,指定内容类型为JSON\r\n    headers = {\r\n        \"Content-Type\": \"application/json\"\r\n    }\r\n\r\n    try:\r\n        # 发送POST请求到注册URL,请求体序列化为JSON格式\r\n        response = requests.post(\"https://wdev.wmj.com.cn/deviceApi/register\", headers=headers, data=json.dumps(body))\r\n        # 检查HTTP响应状态码,确保请求成功\r\n        response.raise_for_status()\r\n        # 解析响应的JSON内容\r\n        response_json = response.json()\r\n        # 记录成功的请求日志\r\n        logging.info(\"请求成功: %s\", response_json)\r\n    except requests.exceptions.RequestException as e:\r\n        # 处理请求异常,记录错误日志\r\n        logging.error(\"请求失败: %s\", e)\r\n    except ValueError as e:\r\n        # 处理解析响应异常,记录错误日志\r\n        logging.error(\"解析响应失败: %s\", e)\r\n\r\n# 云喇叭设备设置函数\r\nimport requests\r\ndef cloud_voice_setting(app_id, app_secret, device_sn):\r\n    \"\"\"\r\n    设置云语音功能。\r\n\r\n    :param app_id: 应用ID\r\n    :param app_secret: 应用密钥\r\n    :param device_sn: 设备序列号\r\n    :return: 服务器响应结果\r\n    \"\"\"\r\n    url = \"https://wdev.wmj.com.cn/deviceApi/send\"\r\n\r\n    # 写死的data参数\r\n    data = {\r\n        \"cmd_type\": \"setting\",\r\n        \"info\": {\r\n            \"volume\": 10,  # 0-9,音量由小到大,默认为中间值\r\n            \"speed\": 2,  # 0-9,语速由慢到快,默认为中间值正常语速\r\n            \"tone\": 4,  # 0-9,语调由低到高,默认为中间值正常语调\r\n            \"speaker\": 0 # 0为女生,支持中英文\r\n        }\r\n    }\r\n\r\n    # 构建请求体\r\n    payload = {\r\n        \"app_id\": app_id,\r\n        \"app_secret\": app_secret,\r\n        \"device_sn\": device_sn,\r\n        \"data\": data\r\n    }\r\n\r\n    # 发送POST请求\r\n    try:\r\n        response = requests.post(url, json=payload)\r\n        response.raise_for_status()  # 如果响应状态码不是200,抛出异常\r\n        logging.info(response.json())  # 打印响应的JSON数据\r\n    except requests.exceptions.RequestException as e:\r\n        logging.error({\"status\": \"error\", \"message\": str(e)})\r\n\r\n# 示例调用\r\n# if __name__ == \"__main__\":\r\n#     app_id = os.getenv(\"APP_ID\", \"a98a124c6c3252f6612fc544a0d0fa79\")\r\n#     app_secret = os.getenv(\"APP_SECRET\", \"88bc1ec4eba624f47b2200a4ce8c3852\")\r\n#     device_sn = os.getenv(\"DEVICE_SN\", \"W703BB44444\")\r\n#     cloud_voice_setting(app_id, app_secret, device_sn)\r\n\r\n# 云喇叭设备播放函数\r\ndef play_cloud_voice(app_id, app_secret, device_sn):\r\n    \"\"\"\r\n    播放云语音功能。\r\n\r\n    本函数通过发送HTTP POST请求,触发远程语音设备播放指定的语音内容。\r\n\r\n    参数:\r\n    - app_id: 应用ID,用于标识应用。\r\n    - app_secret: 应用密钥,用于验证应用的身份。\r\n    - device_sn: 设备序列号,用于标识具体的语音设备。\r\n    \"\"\"\r\n    # 注册设备\r\n    voice_device_register(app_id, app_secret, device_sn)\r\n    sleep(5)  # 可以考虑使用异步编程或非阻塞的方式替代\r\n\r\n    # 设置云喇叭的音量以及语速参数\r\n    cloud_voice_setting(app_id, app_secret, device_sn)\r\n    sleep(10)\r\n\r\n    # 定义请求URL\r\n    url = os.getenv(\"CLOUD_VOICE_API_URL\", \"https://wdev.wmj.com.cn/deviceApi/send\")\r\n\r\n    # 构建请求体,包括应用ID、应用密钥、设备序列号和语音播放指令\r\n    body = {\r\n        \"app_id\": app_id,\r\n        \"app_secret\": app_secret,\r\n        \"device_sn\": device_sn,\r\n        \"data\": {\r\n            \"cmd_type\": \"play\",\r\n            \"info\": {\r\n                \"tts\": \"一、二、三、四、五、六、七、八、九、十        一、二、三、四、五、六、七、八、九、十        一、二、三、四、五、六、七、八、九、十、一、二、三、四、五、六、七、八、九、十\",\r\n                \"inner\": 10,  # wifi版特有\r\n                \"volume\": 5  # 4G版本1-7,wifi版1-10\r\n            }\r\n        }\r\n    }\r\n\r\n    # 设置请求头,指定内容类型为JSON\r\n    headers = {\r\n        \"Content-Type\": \"application/json\"\r\n    }\r\n\r\n    try:\r\n        # 发送POST请求\r\n        response = requests.post(url, headers=headers, data=json.dumps(body))\r\n\r\n        # 根据响应状态码判断请求是否成功\r\n        if response.status_code == 200:\r\n            logging.info(f\"请求成功: {response.json()}\")\r\n        else:\r\n            logging.error(f\"请求失败: 状态码 {response.status_code}, 响应内容 {response.text}\")\r\n\r\n    except requests.exceptions.RequestException as e:\r\n        logging.error(f\"请求过程中发生异常: {e}\")\r\n    except json.JSONDecodeError as e:\r\n        logging.error(f\"JSON解析失败: {e}\")\r\n    except Exception as e:\r\n        logging.error(f\"发生未知异常: {e}\")\r\n\r\n# # 示例调用\r\n# if __name__ == \"__main__\":\r\n#     app_id = os.getenv(\"APP_ID\", \"a98a124c6c3252f6612fc544a0d0fa79\")\r\n#     app_secret = os.getenv(\"APP_SECRET\", \"88bc1ec4eba624f47b2200a4ce8c3852\")\r\n#     device_sn = os.getenv(\"DEVICE_SN\", \"W703BB44444\")\r\n#     play_cloud_voice(app_id, app_secret, device_sn)\r\n\r\n# 获取测试报告通过率等参数的函数\r\nimport logging\r\nimport re\r\nfrom selenium.webdriver.common.by import By\r\ndef get_test_result(latest_report, wd):\r\n    \"\"\"\r\n    获取测试结果页面的通过率、失败率和异常率\r\n\r\n    :param latest_report: 测试结果页面的URL\r\n    :param wd: WebDriver实例,用于访问和操作网页\r\n    :return: 包含通过率、失败率和异常率的字典\r\n    \"\"\"\r\n    # 初始化测试结果字典\r\n    test_result = {\r\n        \"pass_percent\": \"\",\r\n        \"fail_percent\": \"\",\r\n        \"exception_percent\": \"\"\r\n    }\r\n\r\n    # 访问测试结果页面\r\n    wd.get(latest_report)\r\n    sleep(5)\r\n\r\n    # 点击简略显示\r\n    safe_click((By.XPATH,\"//div[@id='display_mode']\"), wd)\r\n    sleep(5)\r\n\r\n    # 定义一个函数来获取和解析百分比\r\n    def get_percentage(selector, wd):\r\n        text = elment_get_text(selector, wd)\r\n        logging.info(f\"获取的文本:{text}\")\r\n        match = re.search(r'(\\d+(\\.\\d+)?)%', text)\r\n        if match:\r\n            return match.group(0)\r\n        else:\r\n            logging.error(f\"未找到百分比匹配项,文本内容: {text}\")\r\n            return \"0\"\r\n\r\n    # 获取通过率\r\n    pass_percent = get_percentage((By.CSS_SELECTOR, \"div[class='result_barchart'] div:nth-child(1) span:nth-child(1)\"), wd)\r\n    test_result[\"pass_percent\"] = pass_percent\r\n\r\n    # 获取失败率\r\n    fail_percent = get_percentage(\r\n        (By.CSS_SELECTOR, \"body > div.main_section > div.result > div > div:nth-child(2) > span\"), wd)\r\n    test_result[\"fail_percent\"] = fail_percent\r\n\r\n    # 获取异常率\r\n    exception_percent = get_percentage(\r\n        (By.CSS_SELECTOR, \"body > div.main_section > div.result > div > div:nth-child(3) > span\"), wd)\r\n    test_result[\"exception_percent\"] = exception_percent\r\n    # 输出test_result\r\n    logging.info(test_result)\r\n    print(test_result)\r\n    sleep(5)\r\n\r\n    # 返回测试结果字典\r\n    return test_result\r\n\r\n# if __name__ == \"__main__\":\r\n#     browser_init(\"展厅预定巡检\")\r\n#     wd = GSTORE['wd']\r\n#     test_result = get_test_result(\"http://nat.ubainsyun.com:31134/report_20250217_094401.html\",wd)\r\n#     print(test_result)\r\n\r\n# 获取本机IP地址函数\r\nimport yaml\r\nimport logging\r\nimport socket\r\nimport subprocess\r\ndef get_local_ip():\r\n    \"\"\"\r\n    获取本机的局域网IP地址。\r\n\r\n    此函数通过尝试与外部网络通信来确定本机的局域网IP地址。它利用了UDP协议,\r\n    并连接到一个知名的公共IP地址和端口,以此来获取本机的IP地址信息。\r\n\r\n    Returns:\r\n        str: 本机的局域网IP地址。\r\n    \"\"\"\r\n    try:\r\n        # 创建一个UDP套接字\r\n        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\r\n        # 连接到一个公共的IP地址和端口\r\n        sock.connect((\"8.8.8.8\", 80))\r\n        # 获取本地IP地址\r\n        local_ip = sock.getsockname()[0]\r\n    finally:\r\n        # 确保在所有情况下关闭套接字\r\n        sock.close()\r\n    return local_ip\r\n\r\n# 更新ngrok.cfg文件中的IP地址 函数\r\ndef update_ngrok_config(config_path, new_ip):\r\n    \"\"\"\r\n    更新ngrok配置文件中的IP地址\r\n\r\n    本函数尝试打开并解析ngrok配置文件,更新其中的IP地址,然后将更改保存回配置文件中\r\n\r\n    参数:\r\n    config_path (str): ngrok配置文件的路径\r\n    new_ip (str): 需要更新到配置文件中的新IP地址\r\n\r\n    返回:\r\n    无\r\n    \"\"\"\r\n    try:\r\n        # 打开并安全地加载ngrok配置文件\r\n        with open(config_path, 'r', encoding='utf-8') as file:\r\n            config = yaml.safe_load(file)\r\n\r\n        # 更新IP地址\r\n        config['tunnels']['nat1']['proto']['tcp'] = f\"{new_ip}:80\"\r\n\r\n        # 将更新后的配置安全地写回文件\r\n        with open(config_path, 'w', encoding='utf-8') as file:\r\n            yaml.safe_dump(config, file, default_flow_style=False, allow_unicode=True)\r\n\r\n        # 记录成功更新的日志信息\r\n        logging.info(f\"ngrok.cfg 更新成功,新的IP地址为: {new_ip}\")\r\n    except Exception as e:\r\n        # 记录更新过程中出现的错误\r\n        logging.error(f\"更新ngrok.cfg文件时出错: {e}\")\r\n\r\n# 启动ngrok函数\r\ndef start_ngrok(ngrok_path, config_path):\r\n    \"\"\"\r\n    启动ngrok工具。\r\n\r\n    在尝试启动ngrok之前,此函数会先终止已运行的ngrok进程(如果有的话)。\r\n    然后使用指定的配置文件路径启动ngrok,并记录启动结果。\r\n\r\n    参数:\r\n    ngrok_path (str): ngrok可执行文件的路径。\r\n    config_path (str): ngrok配置文件的路径。\r\n\r\n    返回:\r\n    无返回值。\r\n    \"\"\"\r\n    try:\r\n        # 终止已运行的ngrok进程\r\n        kill_ngrok()\r\n\r\n        # 构建启动ngrok的命令\r\n        command = [ngrok_path, '-config', config_path, 'start', 'nat1']\r\n        # 使用构建的命令启动ngrok\r\n        subprocess.Popen(command, shell=True)\r\n        # 记录ngrok启动成功的信息\r\n        logging.info(f\"ngrok 启动成功\")\r\n    except Exception as e:\r\n        # 记录启动ngrok时发生的错误\r\n        logging.error(f\"启动ngrok时出错: {e}\")\r\n\r\n# 停止ngrok进程函数\r\ndef kill_ngrok():\r\n    \"\"\"\r\n    尝试终止所有正在运行的ngrok进程。\r\n    \"\"\"\r\n    try:\r\n        # 使用 taskkill 命令终止所有 ngrok 进程\r\n        subprocess.run(['taskkill', '/F', '/IM', 'ngrok.exe'], check=True)\r\n        logging.info(\"终止所有 ngrok 进程成功\")\r\n    except subprocess.CalledProcessError as e:\r\n        # 如果没有找到 ngrok 进程,记录相关信息\r\n        logging.info(\"没有找到 ngrok 进程\")\r\n    except Exception as e:\r\n        # 如果终止 ngrok 进程时发生其他错误,记录错误信息\r\n        logging.error(f\"终止 ngrok 进程时出错: {e}\")\r\n\r\n# if __name__ == '__main__':\r\n#     logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\r\n#\r\n#     # 获取本机IP地址\r\n#     local_ip = get_local_ip()\r\n#     logging.info(f\"本机IP地址: {local_ip}\")\r\n#\r\n#     # 更新ngrok.cfg文件\r\n#     ngrok_config_path = r'D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\ngrok\\ngrok-调试主机\\ngrok.cfg'\r\n#     update_ngrok_config(ngrok_config_path, local_ip)\r\n#\r\n#     # 启动ngrok\r\n#     ngrok_path = r'D:\\GithubData\\自动化\\ubains-module-test\\预定系统\\ngrok\\ngrok-调试主机\\ngrok.exe'\r\n#     start_ngrok(ngrok_path, ngrok_config_path)\r\n\r\n# 字符串转换枚举类型函数\r\ndef get_by_enum(type_str):\r\n    \"\"\"\r\n    将字符串类型的定位器类型转换为 selenium.webdriver.common.by.By 枚举类型。\r\n\r\n    参数:\r\n    type_str (str): 定位器类型字符串,例如 'XPATH'。\r\n\r\n    返回:\r\n    selenium.webdriver.common.by.By: 对应的 By 枚举类型。\r\n    \"\"\"\r\n    # 将输入的定位器类型字符串转换为大写,以匹配 By 枚举类型的命名\r\n    type_str = type_str.upper()\r\n\r\n    # 根据输入的字符串类型返回对应的 By 枚举类型\r\n    if type_str == 'XPATH':\r\n        return By.XPATH\r\n    elif type_str == 'ID':\r\n        return By.ID\r\n    elif type_str == 'NAME':\r\n        return By.NAME\r\n    elif type_str == 'CLASS_NAME':\r\n        return By.CLASS_NAME\r\n    elif type_str == 'CSS_SELECTOR':\r\n        return By.CSS_SELECTOR\r\n    elif type_str == 'TAG_NAME':\r\n        return By.TAG_NAME\r\n    elif type_str == 'LINK_TEXT':\r\n        return By.LINK_TEXT\r\n    elif type_str == 'PARTIAL_LINK_TEXT':\r\n        return By.PARTIAL_LINK_TEXT\r\n    else:\r\n        # 如果输入的定位器类型字符串不匹配任何已知的 By 枚举类型,抛出 ValueError 异常\r\n        raise ValueError(f\"未知的定位器类型: {type_str}\")\r\n\r\n# 获取当前时间并格式化为 'HH:MM' 格式的函数,用于会议预定使用\r\nimport datetime\r\ndef get_current_time_formatted():\r\n    \"\"\"\r\n    获取当前时间并格式化为 'HH:MM' 格式,并选择最近的未来时间点(如 00:00, 00:15, 00:30, 00:45 等)。\r\n\r\n    返回:\r\n    str: 最近的未来时间点字符串,例如 '17:00'。\r\n    \"\"\"\r\n    # 获取当前时间\r\n    current_time = datetime.datetime.now()\r\n    current_time_formatted = current_time.strftime(\"%H:%M\")\r\n\r\n    # 定义时间点列表\r\n    time_points = [\r\n        \"00:00\", \"00:15\", \"00:30\", \"00:45\",\r\n        \"01:00\", \"01:15\", \"01:30\", \"01:45\",\r\n        \"02:00\", \"02:15\", \"02:30\", \"02:45\",\r\n        \"03:00\", \"03:15\", \"03:30\", \"03:45\",\r\n        \"04:00\", \"04:15\", \"04:30\", \"04:45\",\r\n        \"05:00\", \"05:15\", \"05:30\", \"05:45\",\r\n        \"06:00\", \"06:15\", \"06:30\", \"06:45\",\r\n        \"07:00\", \"07:15\", \"07:30\", \"07:45\",\r\n        \"08:00\", \"08:15\", \"08:30\", \"08:45\",\r\n        \"09:00\", \"09:15\", \"09:30\", \"09:45\",\r\n        \"10:00\", \"10:15\", \"10:30\", \"10:45\",\r\n        \"11:00\", \"11:15\", \"11:30\", \"11:45\",\r\n        \"12:00\", \"12:15\", \"12:30\", \"12:45\",\r\n        \"13:00\", \"13:15\", \"13:30\", \"13:45\",\r\n        \"14:00\", \"14:15\", \"14:30\", \"14:45\",\r\n        \"15:00\", \"15:15\", \"15:30\", \"15:45\",\r\n        \"16:00\", \"16:15\", \"16:30\", \"16:45\",\r\n        \"17:00\", \"17:15\", \"17:30\", \"17:45\",\r\n        \"18:00\", \"18:15\", \"18:30\", \"18:45\",\r\n        \"19:00\", \"19:15\", \"19:30\", \"19:45\",\r\n        \"20:00\", \"20:15\", \"20:30\", \"20:45\",\r\n        \"21:00\", \"21:15\", \"21:30\", \"21:45\",\r\n        \"22:00\", \"22:15\", \"22:30\", \"22:45\",\r\n        \"23:00\", \"23:15\", \"23:30\", \"23:45\"\r\n    ]\r\n\r\n    # 将当前时间转换为 datetime 对象\r\n    current_time_dt = datetime.datetime.strptime(current_time_formatted, \"%H:%M\")\r\n\r\n    # 初始化最近时间点和最小时间差\r\n    closest_time_point = None\r\n    min_time_diff = float('inf')\r\n\r\n    # 遍历时间点列表,找到最近的未来时间点\r\n    for time_point in time_points:\r\n        time_point_dt = datetime.datetime.strptime(time_point, \"%H:%M\")\r\n\r\n        # 如果时间点在当前时间之后\r\n        if time_point_dt > current_time_dt:\r\n            time_diff = (time_point_dt - current_time_dt).total_seconds()\r\n            if time_diff < min_time_diff:\r\n                min_time_diff = time_diff\r\n                closest_time_point = time_point\r\n\r\n    # 如果没有找到未来的时间点(即当前时间已经是最后一个时间点),则选择下一个天的最早时间点\r\n    if closest_time_point is None:\r\n        closest_time_point = time_points[0]\r\n\r\n    return closest_time_point\r\n\r\n# 会议创建函数\r\ndef meeting_message(meeting_room_name, message_type, message_name, wd):\r\n    \"\"\"\r\n    会议室会议预定功能的实现。\r\n\r\n    该函数通过模拟用户交互来预定会议室会议。它首先搜索指定的会议室,然后填写会议信息,\r\n    包括会议名称和类型,最后选择会议时间并完成预定。\r\n\r\n    参数:\r\n    - MeetingRoomName (str): 会议室名称,用于搜索指定的会议室。\r\n    - MessageType (str): 会议类型,用于填写会议信息。\r\n    - wd: WebDriver实例,用于操作浏览器。\r\n\r\n    返回:\r\n    无返回值。\r\n    \"\"\"\r\n    # 先搜索会议室\r\n    safe_click((By.XPATH, \"//i[@class='el-collapse-item__arrow el-icon-arrow-right']\"), wd)\r\n    sleep(1)\r\n\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入会议室名称']\"), meeting_room_name, wd)\r\n    INFO(f\"搜索结果为:{meeting_room_name}\")\r\n    # 点击【查询】按钮\r\n    safe_click((By.XPATH, \"//span[contains(text(),'查询')]\"), wd)\r\n    sleep(2)\r\n    # 点击【会议预定】按钮\r\n    safe_click((By.XPATH, \"//span[@class='MeetingCityList_t_btn']\"), wd)\r\n    sleep(2)\r\n    # 输入会议名称并勾选MessageType类型\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入会议名称']\"), message_name, wd)\r\n    safe_click(\r\n        (By.XPATH, f\"//div[@class='reserve_input']//span[@class='el-checkbox__label'][contains(text(),'{message_type}')]\"), wd)\r\n    sleep(1)\r\n    # 选择会议时间,点击【快速预定】按钮\r\n    current_time = get_current_time_formatted()\r\n    print(f\"获取当前的时间{current_time}\")\r\n    safe_click((By.XPATH, f\"//div[normalize-space()='{current_time}']\"), wd)\r\n    sleep(1)\r\n    safe_click((By.XPATH, \"//div[@class='header_Quick']\"), wd)\r\n    safe_click((By.XPATH, \"//div[@class='header_Quick']\"), wd)\r\n    sleep(2)\r\n    # 点击【确定】按钮\r\n    safe_click((By.XPATH, \"//button[@type='button']//span[contains(text(),'预定')]\"), wd)\r\n    sleep(2)\r\n\r\n# 会议状态设置函数\r\ndef message_satus_control(message_name, message_type, control_type, wd):\r\n    \"\"\"\r\n    结束会议流程。\r\n\r\n    参数:\r\n    - message_name: 会议名称,用于搜索特定的会议。\r\n    - message_type: 会议类型,用于判断是否需要进行额外的确认操作。\r\n    - control_type: 控制类型,用于选择相应的会议控制操作。\r\n    - wd: WebDriver实例,用于操作浏览器。\r\n\r\n    该函数通过模拟用户交互来结束会议,包括搜索会议、点击相关按钮等操作。\r\n    \"\"\"\r\n    # 输入会议名称并搜索\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='输入关键字搜索']\"), message_name, wd)\r\n    send_keyboard((By.XPATH, \"//input[@placeholder='输入关键字搜索']\"), wd)\r\n    sleep(2)\r\n    # 点击【更多操作】结束会议数据\r\n    # 提前开始会议\r\n    safe_click((By.XPATH, \"//span[contains(text(),'更多操作')]\"), wd)\r\n    sleep(1)\r\n    safe_click((By.XPATH, \"//li[contains(text(),'会议状态')]\"), wd)\r\n    sleep(1)\r\n    # 选择提前结束\r\n    safe_click((By.XPATH, f\"//span[contains(text(),'{control_type}')]\"), wd)\r\n    sleep(1)\r\n    safe_click((By.XPATH, \"//div[@slot='footer']//span[contains(text(),'确定')]\"), wd)\r\n    sleep(2)\r\n    # 针对特定会议平台的额外确认操作\r\n    if message_type == \"会控\":\r\n        safe_click((By.XPATH,\r\n                    \"//button[@class='el-button el-button--default el-button--small el-button--primary ']//span[contains(text(),'确定')]\"),\r\n                   wd)\r\n        sleep(1)\r\n\r\n# 进入会议预约界面函数\r\ndef enter_meeting_booking_page(meeting_room_name, wd):\r\n    \"\"\"\r\n    进入会议室预订页面并填写会议信息。\r\n\r\n    :param meeting_room_name: 会议室名称,用于搜索特定的会议室。\r\n    :param wd: WebDriver实例,用于操作浏览器。\r\n    \"\"\"\r\n    # 先搜索会议室\r\n    safe_click((By.XPATH, \"//i[@class='el-collapse-item__arrow el-icon-arrow-right']\"), wd)\r\n    sleep(1)\r\n\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入会议室名称']\"), meeting_room_name, wd)\r\n    INFO(f\"搜索结果为:{meeting_room_name}\")\r\n    # 点击【查询】按钮\r\n    safe_click((By.XPATH, \"//span[contains(text(),'查询')]\"), wd)\r\n    sleep(2)\r\n    # 点击【会议预定】按钮\r\n    safe_click((By.XPATH, \"//span[@class='MeetingCityList_t_btn']\"), wd)\r\n    sleep(2)\r\n    # 输入会议名称并勾选MessageType类型\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='请输入会议名称']\"), \"测试\", wd)\r\n    sleep(1)\r\n    # 选择会议时间\r\n    current_time = get_current_time_formatted()\r\n    print(f\"获取当前的时间{current_time}\")\r\n    safe_click((By.XPATH, f\"//div[normalize-space()='{current_time}']\"), wd)\r\n    sleep(1)\r\n\r\n# 删除会议函数\r\ndef del_message(message_name, wd):\r\n    \"\"\"\r\n    删除会议消息。\r\n\r\n    根据会议名称搜索并删除会议。\r\n\r\n    参数:\r\n    - message_name: 会议名称,用于搜索特定的会议。\r\n    - wd: WebDriver实例,用于执行网页自动化操作。\r\n\r\n    此函数无返回值。\r\n    \"\"\"\r\n    # 输入会议名称并搜索\r\n    safe_send_keys((By.XPATH, \"//input[@placeholder='输入关键字搜索']\"), message_name, wd)\r\n    send_keyboard((By.XPATH, \"//input[@placeholder='输入关键字搜索']\"), wd)\r\n    sleep(2)\r\n    # 点击【删除会议】按钮\r\n    safe_click((By.XPATH, \"//span[contains(text(),'删除会议')]\"), wd)\r\n    sleep(2)\r\n    # 点击【确定】按钮\r\n    safe_click((By.XPATH, \"//button[contains(@class,'el-button el-button--default el-button--small el-button--primary')]//span[contains(text(),'确定')]\"), wd)\r\n    sleep(1)\r\n    # 进入【会议室列表】界面\r\n    safe_click((By.XPATH, \"//span[contains(text(),'会议室列表')]\"), wd)\r\n    sleep(1)
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/统一平台/base/bases.py b/统一平台/base/bases.py
--- a/统一平台/base/bases.py	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/统一平台/base/bases.py	(date 1744371131410)
@@ -211,6 +211,7 @@
         element = WebDriverWait(wd, 5).until(EC.visibility_of_element_located(element_locator))
         element.clear()  # 清除元素的当前值
         element.send_keys(value)  # 向元素发送指定的键值
+        sleep(3)
     except TimeoutException:
         # 如果元素在指定时间内未被找到或不可点击,打印超时异常信息
         INFO(f"超时异常:元素 {element_locator} 在20秒内未找到或无法点击。")
@@ -465,13 +466,84 @@
 
 # 读取测试用例xlsx文件中的JSON数据进行数据驱动函数
 import openpyxl
-def read_xlsx_data(xlsx_file_path, sheet_name=None):
+# def read_xlsx_data(xlsx_file_path, sheet_name=None):
+#     """
+#     读取XLSX文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。
+#
+#     参数:
+#     xlsx_file_path (str): XLSX文件的路径。
+#     sheet_name (str, optional): 工作表的名称。如果未指定,则使用活动工作表。
+#
+#     返回:
+#     list: 包含字典的列表,每个字典包含测试用例的名称和参数。
+#     """
+#     try:
+#         # 打开XLSX文件
+#         workbook = openpyxl.load_workbook(xlsx_file_path)
+#     except FileNotFoundError:
+#         raise FileNotFoundError(f"文件未找到: {xlsx_file_path}")
+#     except Exception as e:
+#         raise Exception(f"无法打开文件: {e}")
+#
+#     # 选择工作表
+#     if sheet_name:
+#         try:
+#             sheet = workbook[sheet_name]
+#         except KeyError:
+#             raise KeyError(f"工作表未找到: {sheet_name}")
+#     else:
+#         sheet = workbook.active
+#
+#     # 读取表头,从第三行开始
+#     headers = [cell.value for cell in sheet[3]]
+#
+#     # 打印表头列名
+#     # INFO(f"表头列名: {headers}")
+#
+#     # 找到表头中名为 'JSON' 的列索引
+#     try:
+#         json_index = headers.index('JSON')
+#     except ValueError as e:
+#         raise ValueError(f"表头中没有找到所需的列: {e}")
+#
+#     ddt_cases = []
+#     # 遍历XLSX文件中的每一行数据,从第四行开始
+#     for row_num, row in enumerate(sheet.iter_rows(min_row=4, values_only=True), start=4):
+#         # 获取 JSON 列的数据
+#         json_data = row[json_index]
+#
+#         # 打印 JSON 数据以进行调试
+#         # INFO(f"行 {row_num} 的 JSON 数据: {json_data}")
+#
+#         # 检查 JSON 数据是否为空
+#         if json_data is None or json_data.strip() == "":
+#             # INFO(f"跳过行 {row_num},JSON 数据为空")
+#             continue
+#
+#         # 解析 JSON 字符串
+#         try:
+#             parsed_json = json.loads(json_data)
+#         except json.JSONDecodeError:
+#             raise ValueError(f"行 {row_num} 的 JSON 数据无法解析: {json_data}")
+#
+#         # 将解析后的 JSON 数据添加到列表中
+#         ddt_cases.append(parsed_json)
+#
+#     # 日志记录:XLSX文件已读取
+#     # INFO("XLSX文件已读取")
+#     # 返回包含所有测试用例数据的列表
+#     return ddt_cases
+
+# 获取当前进程的 CPU 占用率函数
+
+def read_xlsx_data(xlsx_file_path, sheet_name=None, case_type=None):
     """
     读取XLSX文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。
 
     参数:
     xlsx_file_path (str): XLSX文件的路径。
     sheet_name (str, optional): 工作表的名称。如果未指定,则使用活动工作表。
+    case_type (str, optional): 测试用例类型,例如 '标准版' 或 'XX项目需求'。如果未指定,则读取所有测试用例。
 
     返回:
     list: 包含字典的列表,每个字典包含测试用例的名称和参数。
@@ -499,11 +571,16 @@
     # 打印表头列名
     # INFO(f"表头列名: {headers}")
 
-    # 找到表头中名为 'JSON' 的列索引
+    # 找到表头中名为 'JSON' 和 '功能类别' 的列索引
     try:
         json_index = headers.index('JSON')
     except ValueError as e:
         raise ValueError(f"表头中没有找到所需的列: {e}")
+
+    try:
+        category_index = headers.index('版本')
+    except ValueError as e:
+        raise ValueError(f"表头中没有找到所需的列: {e}")
 
     ddt_cases = []
     # 遍历XLSX文件中的每一行数据,从第四行开始
@@ -525,6 +602,13 @@
         except json.JSONDecodeError:
             raise ValueError(f"行 {row_num} 的 JSON 数据无法解析: {json_data}")
 
+        # 获取功能类别
+        category = row[category_index]
+
+        # 检查是否需要过滤测试用例类型
+        if case_type and category != case_type:
+            continue
+
         # 将解析后的 JSON 数据添加到列表中
         ddt_cases.append(parsed_json)
 
@@ -533,7 +617,6 @@
     # 返回包含所有测试用例数据的列表
     return ddt_cases
 
-# 获取当前进程的 CPU 占用率函数
 def get_cpu_usage(interval=1):
     """
     获取当前进程的 CPU 占用率。
Index: 运维集控/项目测试/运维标准版/cases/01登录模块/管理员登录.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>from hytest.common import SELENIUM_LOG_SCREEN\r\nfrom win32trace import flush\r\n\r\nfrom 运维集控.项目测试.运维标准版.lib.base import *\r\n\r\n#构建当前项目路径\r\nsys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..')))\r\n# 构建 CSV 文件的绝对路径\r\ncsv_path = os.path.abspath(\r\n    os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', '01登录模块', '登录信息.csv'))\r\n\r\nclass User_Login:\r\n    tags = ['用户登录','管理员登录']\r\n    #构建框架的数据集格式\r\n    ddt_cases = read_csv_data(csv_path)\r\n\r\n    def teststeps(self):\r\n        # 获取 WebDriver 实例\r\n        wd = GSTORE['wd']\r\n\r\n        #从self.para中解构出数据\r\n        name = self.name\r\n        username, password, captcha, info= self.para\r\n\r\n        # 记录当前测试步骤:用户登录\r\n        STEP(1, f'{username} 用户登录测试')\r\n        # 使用从 CSV 读取的数据进行登录\r\n        user_login(username, password, captcha)\r\n\r\n        #记录当前测试步骤:输出提示内容\r\n        STEP(2, f'预期提示内容为:{info}')\r\n        # 记录当前测试步骤:验证是否登录成功\r\n        # 等待系统首页的特定元素(如图片)可见\r\n        get_menu = WebDriverWait(wd, 10).until(\r\n            EC.visibility_of_element_located((By.XPATH, \"//p[@class='el-message__content']\"))\r\n        )\r\n        get_text = get_menu.text\r\n        STEP(3, f'实际获取登录提示内容:{get_text}')\r\n\r\n        # 检查点:验证是否跟预定结果一致\r\n        CHECK_POINT('校验提示内容是否跟预定结果一致', get_text == info)\r\n        # 截图并保存\r\n        SELENIUM_LOG_SCREEN(wd, \"50%\")\r\n        wd.refresh()\r\n
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/cases/01登录模块/管理员登录.py b/运维集控/项目测试/运维标准版/cases/01登录模块/管理员登录.py
--- a/运维集控/项目测试/运维标准版/cases/01登录模块/管理员登录.py	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/运维集控/项目测试/运维标准版/cases/01登录模块/管理员登录.py	(date 1742548002112)
@@ -42,3 +42,4 @@
         # 截图并保存
         SELENIUM_LOG_SCREEN(wd, "50%")
         wd.refresh()
+
Index: 统一平台/cases/主流程功能/03修改会议.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/统一平台/cases/主流程功能/03修改会议.py b/统一平台/cases/主流程功能/03修改会议.py
new file mode 100644
--- /dev/null	(date 1743229416135)
+++ b/统一平台/cases/主流程功能/03修改会议.py	(date 1743229416135)
@@ -0,0 +1,88 @@
+import sys
+import os
+
+# 获取当前脚本的绝对路径
+current_dir = os.path.dirname(os.path.abspath(__file__))
+# 构建统一平台的绝对路径
+platform_path = os.path.abspath(os.path.join(current_dir, '..','..','..'))
+# 添加路径
+sys.path.append(platform_path)
+# 导入模块
+from 统一平台.base.bases import *
+
+class Unified_Platform_0001:
+    tags = ['统一平台','修改本地会议']
+    def teststeps(self):
+        wd = GSTORE['wd']
+        safe_click((By.XPATH, "//p[contains(text(),'新建会议')]"),wd)
+        wd.switch_to.window(wd.window_handles[1])
+        # 选择会议室
+        INFO("选择多会议室与参会人")
+        safe_send_keys((By.XPATH, "//input[@placeholder='请输入会议室名称']"), '测试会议室', wd)
+        send_keyboard((By.XPATH, "//input[@placeholder='请输入会议室名称']"), wd)
+        # 选择测试会议室的前三个
+        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[1]/td[1]/div[1]/label[1]/span[1]/span[1]"),wd)
+        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[2]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
+        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[3]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
+
+        # 选择参会人分配到第一个会议室
+        safe_click((By.XPATH, "(//img)[7]"),wd)
+        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), 'pgy', wd)
+        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
+        # 全选搜索的参会人
+        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
+        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
+
+        # 选择参会人分配到第二个会议室
+        safe_click((By.XPATH, "(//img)[9]"),wd)
+        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), '测试', wd)
+        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
+        # 全选搜索的参会人
+        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
+        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
+
+        # 会议创建,并查看详情
+        safe_click((By.XPATH, "//span[contains(text(),'确定创建')]"),wd)
+        safe_click((By.XPATH, "//span[contains(text(),'查看详情')]"), wd)
+        # 切换窗口2,转到会议详情界面
+        # wd.switch_to.window(wd.window_handles[1])
+        INFO("查看会议详情是否正确包含:多会议室、参会人、会议名称、会议时间等信息")
+        SELENIUM_LOG_SCREEN(wd, "75%")
+
+class Unified_Platform_0002:
+    tags = ['统一平台','修改-视讯会议']
+    def teststeps(self):
+        wd = GSTORE['wd']
+        safe_click((By.XPATH, "(//img[@class='el-tooltip item'])[3]"),wd)
+        # 选择会议室
+        INFO("选择多会议室与参会人")
+        safe_send_keys((By.XPATH, "//input[@placeholder='请输入会议室名称']"), '测试会议室', wd)
+        send_keyboard((By.XPATH, "//input[@placeholder='请输入会议室名称']"), wd)
+        # 选择测试会议室的前三个
+        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[1]/td[1]/div[1]/label[1]/span[1]/span[1]"),wd)
+        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[2]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
+        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[3]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
+
+        # 选择参会人分配到第一个会议室
+        safe_click((By.XPATH, "(//img)[7]"),wd)
+        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), 'test', wd)
+        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
+        # 全选搜索的参会人
+        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
+        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
+
+        # 选择参会人分配到第二个会议室
+        safe_click((By.XPATH, "(//img)[9]"),wd)
+        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), 'admin', wd)
+        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
+        # 全选搜索的参会人
+        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
+        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
+
+        # 会议创建,并查看详情
+        safe_click((By.XPATH, "//span[contains(text(),'确定创建')]"),wd)
+        safe_click((By.XPATH, "//span[contains(text(),'查看详情')]"), wd)
+        # 切换窗口2,转到会议详情界面
+        # wd.switch_to.window(wd.window_handles[1])
+        INFO("查看会议详情是否正确包含:多会议室、参会人、会议名称、会议时间等信息")
+        SELENIUM_LOG_SCREEN(wd, "75%")
\ No newline at end of file
Index: 运维集控/项目测试/运维标准版/cases/07类型标签/01新增标签.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/运维集控/项目测试/运维标准版/cases/07类型标签/01新增标签.py b/运维集控/项目测试/运维标准版/cases/07类型标签/01新增标签.py
new file mode 100644
--- /dev/null	(date 1742548002126)
+++ b/运维集控/项目测试/运维标准版/cases/07类型标签/01新增标签.py	(date 1742548002126)
@@ -0,0 +1,85 @@
+import sys
+import os
+
+from hytest.common import SELENIUM_LOG_SCREEN
+
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..')))
+from 运维集控.项目测试.运维标准版.lib.base import *
+
+#构建当前项目路径
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..')))
+# 构建 CSV 文件的绝对路径
+csv_path = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', '03类型标签', '新增标签.csv'))
+
+class AreagroupAdd:
+    tag = ['新增类型标签']
+    ddt_cases = read_csv_data(csv_path)
+    def teststeps(self):
+        wd = GSTORE['wd']
+        # 从self.para中解构出数据
+        name = self.name
+        area_group, group_name, group_address, remark, info = self.para
+
+        STEP(1, '点击新增按钮')
+        areagroup_add = WebDriverWait(wd, 10).until(
+            EC.element_to_be_clickable((By.XPATH, "//div[@class='company-edmit-right']//span[contains(text(),'新增')]"))
+        )
+        areagroup_add.click()
+
+        STEP(2, f'查看并选择类型标签:{area_group}')
+        if area_group:
+            area_group_input = WebDriverWait(wd, 10).until(
+                EC.presence_of_element_located((By.XPATH, "// input[ @ placeholder = '请选择分组']"))
+            )
+            area_group_input.clear()
+            area_group_input.send_keys(area_group)
+            sleep(1)
+            #默认选择第一个内容
+            area_group_select = WebDriverWait(wd, 10).until(
+                EC.presence_of_element_located((By.XPATH, "//div[@class='el-cascader__suggestion-panel el-scrollbar']//li[1]//span[1]"))
+            )
+            area_group_select.click()
+        else:
+            print("group_name 为空,不执行选择区域分组的操作")
+
+        STEP(3, f'填写标签名称:{group_name}')
+        group_name_input = WebDriverWait(wd, 10).until(
+            EC.presence_of_element_located((By.XPATH, "//input[@placeholder='长度1-20个字符']"))
+        )
+        group_name_input.clear()
+        group_name_input.send_keys(group_name)
+
+        STEP(4, f'填写标签排序:{group_address}')
+        group_address_input = WebDriverWait(wd, 10).until(
+            EC.presence_of_element_located((By.XPATH, "//input[@placeholder='输入地址不能大于50个字符']"))
+        )
+        group_address_input.clear()
+        group_address_input.send_keys(group_address)
+
+        STEP(5, f'填写分组备注:{remark}')
+        group_remark = WebDriverWait(wd, 10).until(
+            EC.presence_of_element_located(
+                (By.XPATH, "//input[@placeholder='请输入备注']"))
+        )
+        group_remark.clear()
+        group_remark.send_keys(remark)
+
+        STEP(6, '点击确认')
+        commit = WebDriverWait(wd, 10).until(
+            EC.element_to_be_clickable((By.XPATH, "//div[@class='dialog-footer']//span[contains(text(),'确 定')]"))
+        )
+        commit.click()
+
+        STEP(7, '验证是否新增成功')
+        get_menu = WebDriverWait(wd, 10).until(
+            EC.visibility_of_element_located((By.XPATH, "//p[@class='el-message__content']"))
+        )
+        get_menu1 = get_menu.text
+        CHECK_POINT('检查是否出现成功提示弹窗', get_menu1 == info )
+
+        # 截图并保存
+        SELENIUM_LOG_SCREEN(wd, "50%")
+        sleep(1)
+        wd.refresh()
+
Index: 统一平台/cases/主流程功能/01创建本地会议.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>import sys\r\nimport os\r\n\r\n# 获取当前脚本的绝对路径\r\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\r\n# 构建统一平台的绝对路径\r\nplatform_path = os.path.abspath(os.path.join(current_dir, '..','..','..'))\r\n# 添加路径\r\nsys.path.append(platform_path)\r\n# 导入模块\r\nfrom 统一平台.base.bases import *\r\n\r\nclass Unified_Platform_0001:\r\n    tags = ['统一平台','创建-本地会议-立即开始']\r\n    def teststeps(self):\r\n        wd = GSTORE['wd']\r\n        safe_click((By.XPATH, \"//p[contains(text(),'新建会议')]\"),wd)\r\n        wd.switch_to.window(wd.window_handles[1])\r\n        # 选择会议室\r\n        INFO(\"选择多会议室与参会人\")\r\n        safe_send_keys((By.XPATH, \"//input[@placeholder='请输入会议室名称']\"), '测试会议室', wd)\r\n        send_keyboard((By.XPATH, \"//input[@placeholder='请输入会议室名称']\"), wd)\r\n        # 选择测试会议室的前三个\r\n        safe_click((By.XPATH, \"//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[1]/td[1]/div[1]/label[1]/span[1]/span[1]\"),wd)\r\n        safe_click((By.XPATH, \"//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[2]/td[1]/div[1]/label[1]/span[1]/span[1]\"), wd)\r\n        safe_click((By.XPATH, \"//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[3]/td[1]/div[1]/label[1]/span[1]/span[1]\"), wd)\r\n\r\n        # 选择参会人分配到第一个会议室\r\n        safe_click((By.XPATH, \"(//img)[7]\"),wd)\r\n        safe_send_keys((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), 'pgy', wd)\r\n        send_keyboard((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), wd)\r\n        # 全选搜索的参会人\r\n        safe_click((By.XPATH, \"//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]\"),wd)\r\n        safe_click((By.XPATH, \"//span[contains(text(),'完成')]\"),wd)\r\n\r\n        # 选择参会人分配到第二个会议室\r\n        safe_click((By.XPATH, \"(//img)[9]\"),wd)\r\n        safe_send_keys((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), '测试', wd)\r\n        send_keyboard((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), wd)\r\n        # 全选搜索的参会人\r\n        safe_click((By.XPATH, \"//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]\"),wd)\r\n        safe_click((By.XPATH, \"//span[contains(text(),'完成')]\"),wd)\r\n\r\n        # 会议创建,并查看详情\r\n        safe_click((By.XPATH, \"//span[contains(text(),'确定创建')]\"),wd)\r\n        safe_click((By.XPATH, \"//span[contains(text(),'查看详情')]\"), wd)\r\n        # 切换窗口2,转到会议详情界面\r\n        # wd.switch_to.window(wd.window_handles[1])\r\n        INFO(\"查看会议详情是否正确包含:多会议室、参会人、会议名称、会议时间等信息\")\r\n        SELENIUM_LOG_SCREEN(wd, \"75%\")\r\n\r\nclass Unified_Platform_0002:\r\n    tags = ['统一平台','创建-本地会议-预定会议']\r\n    def teststeps(self):\r\n        wd = GSTORE['wd']\r\n        safe_click((By.XPATH, \"(//img[@class='el-tooltip item'])[3]\"),wd)\r\n        # 选择会议室\r\n        INFO(\"选择多会议室与参会人\")\r\n        safe_send_keys((By.XPATH, \"//input[@placeholder='请输入会议室名称']\"), '测试会议室', wd)\r\n        send_keyboard((By.XPATH, \"//input[@placeholder='请输入会议室名称']\"), wd)\r\n        # 选择测试会议室的前三个\r\n        safe_click((By.XPATH, \"//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[1]/td[1]/div[1]/label[1]/span[1]/span[1]\"),wd)\r\n        safe_click((By.XPATH, \"//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[2]/td[1]/div[1]/label[1]/span[1]/span[1]\"), wd)\r\n        safe_click((By.XPATH, \"//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[3]/td[1]/div[1]/label[1]/span[1]/span[1]\"), wd)\r\n\r\n        # 选择参会人分配到第一个会议室\r\n        safe_click((By.XPATH, \"(//img)[7]\"),wd)\r\n        safe_send_keys((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), 'test', wd)\r\n        send_keyboard((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), wd)\r\n        # 全选搜索的参会人\r\n        safe_click((By.XPATH, \"//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]\"),wd)\r\n        safe_click((By.XPATH, \"//span[contains(text(),'完成')]\"),wd)\r\n\r\n        # 选择参会人分配到第二个会议室\r\n        safe_click((By.XPATH, \"(//img)[9]\"),wd)\r\n        safe_send_keys((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), 'admin', wd)\r\n        send_keyboard((By.XPATH, \"//input[contains(@placeholder,'请输入关键字搜索')]\"), wd)\r\n        # 全选搜索的参会人\r\n        safe_click((By.XPATH, \"//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]\"),wd)\r\n        safe_click((By.XPATH, \"//span[contains(text(),'完成')]\"),wd)\r\n\r\n        # 会议创建,并查看详情\r\n        safe_click((By.XPATH, \"//span[contains(text(),'确定创建')]\"),wd)\r\n        safe_click((By.XPATH, \"//span[contains(text(),'查看详情')]\"), wd)\r\n        # 切换窗口2,转到会议详情界面\r\n        # wd.switch_to.window(wd.window_handles[1])\r\n        INFO(\"查看会议详情是否正确包含:多会议室、参会人、会议名称、会议时间等信息\")\r\n        SELENIUM_LOG_SCREEN(wd, \"75%\")
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/统一平台/cases/主流程功能/01创建本地会议.py b/统一平台/cases/主流程功能/01创建本地会议.py
--- a/统一平台/cases/主流程功能/01创建本地会议.py	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/统一平台/cases/主流程功能/01创建本地会议.py	(date 1744370883444)
@@ -10,79 +10,46 @@
 # 导入模块
 from 统一平台.base.bases import *
 
+# 获取当前脚本所在的目录
+current_dir = os.path.dirname(os.path.abspath(__file__))
+
+# 构建XLSX文件的绝对路径
+xlsx_file_path = os.path.join(current_dir, '..', '..', 'data', '统一平台PC端测试用例.xlsx')
+
 class Unified_Platform_0001:
-    tags = ['统一平台','创建-本地会议-立即开始']
+    #执行指令:
+    # cd ./统一平台/
+    # hytest --tag 统一平台
+    tags = ['创建会议']
+    ddt_cases = read_xlsx_data(xlsx_file_path, sheet_name="新建会议&")
     def teststeps(self):
         wd = GSTORE['wd']
-        safe_click((By.XPATH, "//p[contains(text(),'新建会议')]"),wd)
+        # 保存当前窗口句柄
+        original_window = wd.current_window_handle
+        # 进入创建会议的页面
+        safe_click((By.XPATH, "//p[contains(text(),'新建会议')]"), wd)
         wd.switch_to.window(wd.window_handles[1])
-        # 选择会议室
-        INFO("选择多会议室与参会人")
-        safe_send_keys((By.XPATH, "//input[@placeholder='请输入会议室名称']"), '测试会议室', wd)
-        send_keyboard((By.XPATH, "//input[@placeholder='请输入会议室名称']"), wd)
-        # 选择测试会议室的前三个
-        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[1]/td[1]/div[1]/label[1]/span[1]/span[1]"),wd)
-        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[2]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
-        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[3]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
-
-        # 选择参会人分配到第一个会议室
-        safe_click((By.XPATH, "(//img)[7]"),wd)
-        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), 'pgy', wd)
-        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
-        # 全选搜索的参会人
-        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
-        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
-
-        # 选择参会人分配到第二个会议室
-        safe_click((By.XPATH, "(//img)[9]"),wd)
-        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), '测试', wd)
-        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
-        # 全选搜索的参会人
-        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
-        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
-
-        # 会议创建,并查看详情
-        safe_click((By.XPATH, "//span[contains(text(),'确定创建')]"),wd)
-        safe_click((By.XPATH, "//span[contains(text(),'查看详情')]"), wd)
-        # 切换窗口2,转到会议详情界面
-        # wd.switch_to.window(wd.window_handles[1])
-        INFO("查看会议详情是否正确包含:多会议室、参会人、会议名称、会议时间等信息")
-        SELENIUM_LOG_SCREEN(wd, "75%")
-
-class Unified_Platform_0002:
-    tags = ['统一平台','创建-本地会议-预定会议']
-    def teststeps(self):
-        wd = GSTORE['wd']
-        safe_click((By.XPATH, "(//img[@class='el-tooltip item'])[3]"),wd)
-        # 选择会议室
-        INFO("选择多会议室与参会人")
-        safe_send_keys((By.XPATH, "//input[@placeholder='请输入会议室名称']"), '测试会议室', wd)
-        send_keyboard((By.XPATH, "//input[@placeholder='请输入会议室名称']"), wd)
-        # 选择测试会议室的前三个
-        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[1]/td[1]/div[1]/label[1]/span[1]/span[1]"),wd)
-        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[2]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
-        safe_click((By.XPATH, "//body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[4]/div[2]/table[1]/tbody[1]/tr[3]/td[1]/div[1]/label[1]/span[1]/span[1]"), wd)
-
-        # 选择参会人分配到第一个会议室
-        safe_click((By.XPATH, "(//img)[7]"),wd)
-        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), 'test', wd)
-        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
-        # 全选搜索的参会人
-        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
-        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
-
-        # 选择参会人分配到第二个会议室
-        safe_click((By.XPATH, "(//img)[9]"),wd)
-        safe_send_keys((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), 'admin', wd)
-        send_keyboard((By.XPATH, "//input[contains(@placeholder,'请输入关键字搜索')]"), wd)
-        # 全选搜索的参会人
-        safe_click((By.XPATH, "//div[contains(@class,'meeting_user')]//div[contains(@class,'el-table__fixed-header-wrapper')]//span[contains(@class,'el-checkbox__inner')]"),wd)
-        safe_click((By.XPATH, "//span[contains(text(),'完成')]"),wd)
-
-        # 会议创建,并查看详情
-        safe_click((By.XPATH, "//span[contains(text(),'确定创建')]"),wd)
-        safe_click((By.XPATH, "//span[contains(text(),'查看详情')]"), wd)
-        # 切换窗口2,转到会议详情界面
-        # wd.switch_to.window(wd.window_handles[1])
-        INFO("查看会议详情是否正确包含:多会议室、参会人、会议名称、会议时间等信息")
-        SELENIUM_LOG_SCREEN(wd, "75%")
\ No newline at end of file
+        name = self.name
+        for step in self.para:
+            locator_type = get_by_enum(step.get('locator_type'))
+            locator_value = step.get('locator_value')
+            element_type = step.get('element_type')
+            element_value = step.get('element_value')
+            expented_result = step.get('expented_result')
+            print(f"执行JSON:{step}")
+            if step.get("page") == "CreateMeeting":
+                if element_type == "input":
+                    safe_send_keys((locator_type, locator_value), element_value, wd)
+                    sleep(1)
+                elif element_type == "click":
+                    safe_click((locator_type, locator_value), wd)
+                    sleep(1)
+                elif element_type == "getText":
+                    # 获取提示文本
+                    notify_text = elment_get_text((locator_type,locator_value), wd)
+                    INFO(f"获取弹窗提示内容为:{notify_text}")
+                    CHECK_POINT("判断会议是否创建成功", expented_result == notify_text)
+        SELENIUM_LOG_SCREEN(wd, "50%")
+        # 切换回原来的窗口
+        wd.close()  # 关闭当前窗口
+        wd.switch_to.window(original_window)  # 切换回原来的窗口
\ No newline at end of file
Index: .gitignore
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>/预定系统/log\r\n/预定系统/reports/\r\n/预定系统/reports/imgs/
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/.gitignore b/.gitignore
--- a/.gitignore	(revision c38ef6275b0cbb184029da514c84e3c7e99ffc89)
+++ b/.gitignore	(date 1744371707288)
@@ -1,3 +1,3 @@
 /预定系统/log
 /预定系统/reports/
-/预定系统/reports/imgs/
\ No newline at end of file
+/预定系统/reports/imgs/
diff --git a/运维集控/项目测试/运维标准版/cases/08设备管理/04删除设备.py b/运维集控/项目测试/运维标准版/cases/08设备管理/04删除设备.py
new file mode 100644
diff --git a/运维集控/项目测试/运维标准版/cases/07类型标签/03删除标签.py b/运维集控/项目测试/运维标准版/cases/07类型标签/03删除标签.py
new file mode 100644
diff --git a/运维集控/项目测试/运维标准版/cases/08设备管理/03查询设备.py b/运维集控/项目测试/运维标准版/cases/08设备管理/03查询设备.py
new file mode 100644
diff --git a/运维集控/项目测试/运维标准版/cases/08设备管理/02编辑设备.py b/运维集控/项目测试/运维标准版/cases/08设备管理/02编辑设备.py
new file mode 100644
diff --git a/运维集控/项目测试/运维标准版/cases/07类型标签/02编辑标签.py b/运维集控/项目测试/运维标准版/cases/07类型标签/02编辑标签.py
new file mode 100644