自动化框架






Selenium


环境准备

1.包

#下载包
pip install -i https://pypi.douban.com/simple selenium


2.驱动

Chrome for Testing availability

  • Chrome测试版(防自动更新)
  • ChormeDriver




元素定位

  • id 定位

find_element_by_id

# 标签
<input id="toolbar-search-input" autocomplete="off" type="text" value="" placeholder="C++难在哪里?">

# 由于 id 的唯一性,可以不用管其他的标签的内容。
driver.find_element_by_id("toolbar-search-input")

  • name 定位

find_element_by_name

# 标签
<meta name="keywords" content="论坛">

# 定位
driver.find_element_by_name("keywords")

  • class 定位

find_element_by_class_name

# 标签
<div class="toolbar-search-container">

# 定位
driver.find_element_by_class_name("toolbar-search-container")


  • xpath 定位

    xpath 是一种在 XML 文档中定位元素的语言,拥有多种定位方式
<head>...<head/>
<body>
<div id="csdn-toolbar">
<div class="toolbar-inside">
<div class="toolbar-container">
<div class="toolbar-container-left">...</div>
<div class="toolbar-container-middle">
<div class="toolbar-search onlySearch">
<div class="toolbar-search-container">
<input id="toolbar-search-input" autocomplete="off" type="text" value="" placeholder="C++难在哪里?">
# 绝对路径(层级关系)定位
driver.find_element_by_xpath(
"/html/body/div/div/div/div[2]/div/div/input[1]")
# 利用元素属性定位
driver.find_element_by_xpath(
"//*[@id='toolbar-search-input']"))
# 层级+元素属性定位
driver.find_element_by_xpath(
"//div[@id='csdn-toolbar']/div/div/div[2]/div/div/input[1]")
# 逻辑运算符定位
driver.find_element_by_xpath(
"//*[@id='toolbar-search-input' and @autocomplete='off']")


  • css 定位

CSS 使用选择器来为页面元素绑定属性,它可以较为灵活的选择控件的任意属性,一般定位速度比 xpath 要快

方法 例子 描述
.class .toolbar-search-container 选择 class = 'toolbar-search-container' 的所有元素
#id #toolbar-search-input 选择 id = 'toolbar-search-input' 的元素
***** * 选择所有元素
element input 选择所有 <input\> 元素
element>element div>input 选择父元素为 <div\> 的所有 <input\> 元素
element+element div+input 选择同一级中在 <div\> 之后的所有 <input\> 元素
[attribute=value] type='text' 选择 type = 'text' 的所有元素
driver.find_element_by_css_selector('#toolbar-search-input')
driver.find_element_by_css_selector('html>body>div>div>div>div>div>div>input')




简单使用

1.启动调试浏览器

  • 推荐使用bat脚本实现
  • 创建文件夹 【selenium\AutomationProfile】
  • 自定义调试端口 9527
@echo off  
start "" " chrome.exe位置 " --remote-debugging-port=9527 --user-data-dir="D:\selenium\AutomationProfile" --new-window " 接手的网站 "
# 导入Selenium中的webdriver模块,用于驱动浏览器自动化
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoAlertPresentException, TimeoutException,ElementNotInteractableException


2.加载驱动连接浏览器

# 初始化Chrome浏览器的选项对象,用于定制浏览器实例的行为
chrome_options = webdriver.ChromeOptions()

# 添加一个实验性选项,指定连接到已经打开且带有调试器的Chrome实例
# 这里的地址"127.0.0.1:9527"是指定的调试器地址,需确保该地址上有Chrome正在运行并等待连接
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:端口")

# 使用配置好的选项实例化Chrome WebDriver,连接到已有的Chrome实例
driver = webdriver.Chrome(options=chrome_options)

# 切换到浏览器的默认内容,这一步对于新打开的页面不是必须的,
# 但对于已经存在iframe嵌套的情况,有助于确保后续操作在正确的上下文中进行
driver.switch_to.default_content()

# 让浏览器导航到指定的URL地址,这里假设url变量已正确定义
driver.get(url)


3.等待元素显示

#1.Xpath定位等待元素
WebDriverWait(driver, 50).until(
EC.visibility_of_element_located((By.XPATH, '//*[@id="menu"]')))

#2.Xpath定位等待元素内指定文本的控件 [contains(text()," 文本 ")]
WebDriverWait(self.driver, 20).until(
EC.visibility_of_element_located((By.XPATH, f'//*[@id="*"]/div[contains(text(),"{name}")]'))) # 等待人员数据

timeout:最长等待时间,单位为秒。

until(method, message=’’):

  • method:返回值为True或False的可调用方法(如lambda函数或自定义函数)。
  • message:可选参数,如果等待超时,将抛出TimeoutException异常时显示的消息。

locator:元组,包含两个元素,第一个是定位方法(如By.XPATH),第二个是定位表达式



4.点击

#1.点击指定Xpath定位的元素
driver.find_element(by=By.XPATH, value='//*[@id="menu"]').click()

#2.点击通过元素的部分ID属性定位并点击按钮
driver.find_element(by=By.XPATH, value='//*[contains(@id,"*")]').click()
driver.find_element(by=By.XPATH, value='//*[contains(@class,"*")]').click()
driver.find_element(by=By.XPATH, value='//*[contains(@name,"*")]').click()

**contains()**:函数用来判断属性值是否包含特定的字符串,这里用于模糊匹配ID。



5.切换 iframe 框架

使用WebDriverWait配合预期条件(Expected Condition)来智能等待并切换到指定的iframe框架

WebDriverWait(driver, 50).until(EC.frame_to_be_available_and_switch_to_it((By.ID, "iframebox1")))

driver:当前WebDriver实例,用于与浏览器交互。

50:等待的最长时间(单位为秒),在此期间会不断检查条件是否满足。

frame_to_be_available_and_switch_to_it

  • 指定的iframe是否存在并且可交互(即“可用”)
  • 当iframe可用时,自动切换到该iframe,使得后续的定位操作在该iframe的上下文中执行
  • (By.ID, “iframebox1”):这是定位iframe的方式和参数,通过ID定位,参数为”iframebox1”


6.获取标签的属性/文本

driver.find_element(by=By.XPATH, value='//*[@id="*').text

# 获取属性的值
driver.find_element(by=By.XPATH,value='//*[@id="*"][@dicttype="*"]').get_attribute('reltext') #reltext的值
driver.find_element(by=By.XPATH,value='//div[contains(@class,"*")]/span[contains(text(),"*)"]').get_attribute('id')
tr.get_attribute("class")


浏览器控制

# 刷新页面
driver.refresh()
# 当前页面跳转到新的窗口
# 先获取当前各个窗口的句柄,这些信息的保存顺序是按照时间来的,最新打开的窗口放在数组的末尾,这时就可以定位到最新打开的那个窗口了
# 获取打开的多个窗口句柄
windows = driver.window_handles
# 切换到当前最新打开的窗口
driver.switch_to.window(windows[-1])


# 在点击前跳转的时候保存当前窗口句柄
original_window = driver.current_window_handle
# 点击操作后
WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2))
# 动态获取新窗口句柄
all_windows_after_click = driver.window_handles
if len(all_windows_after_click) > 1:
new_window = [window for window in all_windows_after_click if window != original_window][0]
driver.switch_to.window(new_window)
print('进入新窗口')
else:
print("新窗口未按预期打开")
# 操作完成后关闭当前窗口(B系统)
driver.close()

# 切换回A系统的窗口
driver.switch_to.window(original_window)
方法 描述
send_keys() 模拟输入指定内容
clear() 清除文本内容
is_displayed() 判断该元素是否可见
get_attribute() 获取标签属性值
size 返回元素的尺寸
text 返回元素文本




鼠标控制

方法 描述
click() 单击左键
context_click() 单击右键
double_click() 双击
drag_and_drop() 拖动
move_to_element() 鼠标悬停
perform() 执行所有ActionChains中存储的动作
################### 单击左键
# 定位搜索按钮
button = driver.find_element_by_xpath('//*[@id="toolbar-search-button"]/span')
# 执行单击操作
button.click()


################### 单击右键
from selenium.webdriver.common.action_chains import ActionChains
# 定位搜索按钮
button = driver.find_element_by_xpath('//*[@id="toolbar-search-button"]/span')
# 右键搜索按钮
ActionChains(driver).context_click(button).perform()


################### 双击
# 定位搜索按钮
button = driver.find_element_by_xpath('//*[@id="toolbar-search-button"]/span')
# 执行双击动作
ActionChains(driver).double_click(button).perform()

################### 鼠标悬停
# 定位收藏栏
collect = driver.find_element_by_xpath('//*[@id="csdn-toolbar"]/div/div/div[3]/div/div[3]/a')
# 悬停至收藏标签处
ActionChains(driver).move_to_element(collect).perform()




键盘控制

from selenium.webdriver.common.keys import Keys

# 定位输入框并输入文本
driver.find_element_by_id('xxx').send_keys('Dream丶killer')

# 模拟回车键进行跳转(输入内容后)
driver.find_element_by_id('xxx').send_keys(Keys.ENTER)

# 使用 Backspace 来删除一个字符
driver.find_element_by_id('xxx').send_keys(Keys.BACK_SPACE)

# Ctrl + A 全选输入框中内容
driver.find_element_by_id('xxx').send_keys(Keys.CONTROL, 'a')

# Ctrl + C 复制输入框中内容
driver.find_element_by_id('xxx').send_keys(Keys.CONTROL, 'c')

# Ctrl + V 粘贴输入框中内容
driver.find_element_by_id('xxx').send_keys(Keys.CONTROL, 'v')
操作 描述
Keys.F1 F1键
Keys.SPACE 空格
Keys.TAB Tab键
Keys.ESCAPE ESC键
Keys.ALT Alt键
Keys.SHIFT Shift键
Keys.ARROW_DOWN 向下箭头
Keys.ARROW_LEFT 向左箭头
Keys.ARROW_RIGHT 向右箭头




工作技巧

  • 遍历table找到期望行数据
WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located(
(By.XPATH, '//*[@id="*"]/div/table')))
print('--table装载完成--')

table = WebDriverWait(self.driver, 20).until(EC.presence_of_element_located((By.XPATH, '//*[@id=" * "]/div/table')))
tbody = table.find_element(By.TAG_NAME,"tbody")
for tr in tbody.find_elements(By.TAG_NAME,"tr"):
# print(tr.get_attribute("class"))
if 'ant-table-row ant-table-row-level-0' in tr.get_attribute("class"):
td_check = tr.find_elements(By.TAG_NAME, "td")[5] #获取数据
if td_check.text.strip() == "定位数据":
td_data = tr.find_elements(By.TAG_NAME, "td")[6].text
name = td_data.split('/',1)[0]
bu_men = tr.find_elements(By.TAG_NAME, "td")[7].text


  • 跳层标签定位
sl_id = self.driver.find_element(by=By.XPATH,
value='//div[contains(@class,"*")]/span[contains(text(),"*")]/..//input[@class="*"]').get_attribute('id')







PyWinAuto

控制软件的第一件事就是启动一个Windows软件,每一个软件(进程)都是一个Application对象

实例化Application对象的时候可以传入一个backend参数,可选值为win32(默认)和 uia ,

win32对应的框架:MFC、VB6、VCL、简单的 WinForms 控件和大多数旧的遗留应用程序

uia对应的框架:WinForms、WPF、商店应用程序、Qt5、浏览器

如果无法知道要测试的软件是属于哪种框架,可以使用 Inspect(对应uia) 和 Spy++(对应win32) 看看,哪个显示得更全就选哪个


环境准备

  • 下载
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/

  • 导入引用
from pywinauto.application import Application

  • GUI元素定位软件

image-20240626150947382



简单使用

方法 常用参数 说明
start() cmd_line、timeout、retry_interval 通过cmd命令启动一个软件(进程)
connect() process、handle、path、timeout 连接一个进程,一般是使用进程号(任务管理器可以看到)
top_window() / 获取应用的顶层窗口
window() title、title_re、class_name、best_match 获取单个窗口(WindowSpecification)
windows() title、title_re、class_name 获取多个窗口(UIAWrapper)
active()() / 搜索返回一个激活的窗口
kill() soft 结束进程
wait_for_process_exit() timeout、retry_interval 等待进程结束

1.启动应用

  • start
# 1.打开系统应用  --  名称
app = Application(backend="uia").start("notepad.exe")

# 2.打开自定义应用 -- 路径
app = Application(backend="uia").start(r"D:\Wechat\WeChat.exe")

2.连接应用

  • connect – 使用ViewWizard工具
# 1.通过进程号进行连接
app = Application("uia").connect(process=40936)

# 2.通过窗口句柄进行连接
app = Application("uia").connect(handle=197736)

# 3.通过title进行连接
app = Application(backend="uia").connect(title="微信")

app = Application(backend="uia").start(r"D:\Wechat\WeChat.exe")
app.connect(title="微信")

3.选择窗口

  • app[]
############################### 方式一*   app[类名/标题]
# 使用类名选择窗口
dlg = app["WeChatMainWndForPC"]
# # 打印窗口中的全部控件定位信息
dlg.print_control_identifiers()

# 使用窗口标题选择窗口
dlg = app["微信"]
dlg.print_control_identifiers()


############################### 方式二 app.窗口类名
dlg = app.WeChatMainWndForPC
dlg.print_control_identifiers()

4.操作窗口

#窗口最大化
dlg.maximize()

#窗口最小化
dlg.minimize()

#还原窗口正常大小
dlg.restore()

#查询窗口显示状态 -- 最大化:1,正常:0
status = dlg.get_show_state()

#获取窗口坐标
xy = dlg.rectangle()
print(xy)

#关闭窗口
dlg.close()

5.定位元素

  • child_window

image-20240627095240119

image-20240627100050263


# child_window
dlg.child_window(title="进入微信", control_type="Button").click_input()
dlg.child_window(title="小伙伴", control_type="Button").click_input()

# dlg
btn = dlg['设置及其他']
btn.draw_outline(colour="red")
btn.click_input()

6.等待元素

  • exists
login_button = dlg.child_window(title='进入微信', control_type='Button')
has_btn = login_button.exists(timeout=20)

dlg.child_window(title='进入微信', control_type='Button').exists(timeout=20)


微信单发送案例

from pywinauto.application import Application
from pyautogui import hotkey

# 1.打开
app = Application(backend="uia").start(r"D:\Wechat\WeChat.exe")
# 2.连接
app.connect(title="微信")
# 3.选择窗口
dlg = app["微信"]

# 获取聊天列表中的“文件传输助手”
fileSend = dlg.child_window(title="文件传输助手", control_type="ListItem")
# 点击“文件传输助手”
fileSend.click_input()
# 获取点击聊天窗口
dlg.child_window(title="文件传输助手", control_type="Edit").click_input()
# 输入
dlg.child_window(title="文件传输助手", control_type="Edit").type_keys('测试')
# # 获取发送按钮
# sendButton = dlg.child_window(title="发送(S)", control_type="Button")
# # 点击发送按钮
# sendButton.click_input()
# 使用回车键发送
hotkey('enter')


微信完整流程案例

  • 1.判断是否登录
  • 2.未登录进行点击登录
  • 3.进入界面列表查询聊天对象是否存在
  • 4.聊天对象不存在则搜索
  • 5.进入聊天对象界面
  • 6.输入内容
  • 7.发送
# 判断是否存在进入微信按钮--即登录界面
login_button = dlg.child_window(title='进入微信', control_type='Button')
has_btn = login_button.exists(timeout=20)
if has_btn:
# 存在即未登录--登录操作
login_button.click_input()

#进入界面列表查询聊天对象是否存在
if dlg.child_window(title='小伙伴', control_type='Text').exists(timeout=2):
dlg.child_window(title='小伙伴', control_type='Text').click_input()
else:
# 单击搜索
dlg.child_window(title="搜索", control_type="Edit").click_input()
# 输入信息
dlg.child_window(title="搜索", control_type="Edit").type_keys("RPA运维小伙伴")
# 单击聊天对象
dlg.child_window(best_match="小伙伴", control_type="ListItem").click_input()

# 输入内容
dlg.child_window(title="小伙伴", control_type="Edit").type_keys( "======== PyWinAuto正在操控微信======== ")
hotkey('enter')


本地文件复制到剪切板

# 发送图片资源
def send_image_to_user(image_path):
# 使用Pillow库打开图片文件
img = Image.open(image_path)
# 创建BytesIO对象,用于保存图片的二进制数据,以便写入剪贴板
output = BytesIO()
# 将图片转换为RGB模式并保存为BMP格式至BytesIO对象中
img.convert("RGB").save(output, "BMP")
# 获取BytesIO中的值并裁剪前14个字节(通常为BMP文件头信息),保留图像数据部分
data = output.getvalue()[14:]
# 打印当前读取图片资源的大小信息
print(f'事件: 当前读取的图片资源 - {len(data)} 字节')
# 关闭BytesIO对象
output.close()
# 操作系统交互:打开系统剪贴板
clip.OpenClipboard()
# 清空剪贴板现有内容
clip.EmptyClipboard()
# 将处理后的图像数据写入剪贴板,格式为设备无关位图(DIB)
clip.SetClipboardData(CF_DIB, data)
# 针对大文件的延迟以确保大文件写入完成
time.sleep(7) # 针对大文件的延迟
# 关闭剪贴板操作,释放资源
clip.CloseClipboard()
# 模拟键盘操作粘贴(Ctrl+V)
hotkey('ctrl', 'v')
# 发送
hotkey('enter')


Excel转图片

  • 使用excel2img
# excel转图片-截图方式
def excel_to_image_excel2img(excel_path):
# 要截图的工作表名称
sheet_name = 'Sheet1'

# 保存图片到本地
img_filename = f"{os.path.splitext(os.path.basename(excel_path))[0]}"
if '.' in img_filename:
img_filename = f"{os.path.splitext(img_filename)[0]}"
img_filename += '.png'
img_path = os.path.join(CHROME_DOWNLOADS, img_filename)
save_log("开始截图保存")
excel2img.export_img(excel_path, img_path, sheet_name, None)