正如之前的博文所说,虽然我离开了IT行业,但是对IT的热情还在,最近还在研究Ollema本地部署大模型等。后续发布的文章均是我喜欢的,和工作生活相关的代码,也是我真正感兴趣的没有IT行业职场压力的代码。
其实早就想去写一篇关于"Electron应用自动化测试心得"的博客了,之前我在做Electron应用自动化测试的时候遇到了很多坑,最后借助AI工具最后解决掉了,所以现在想分享出来给大家。之前因为时间的限制,所以一直没开始动笔。现在有时间了,所以来分享一下我针对Electron应用自动化测试的心得,旨在抛砖引玉。后续随着AI的发展,大家可能更多地会去选择使用AI解决学习工作中的各种问题吧,不过,这也是大势所趋。
在去年第一次接触到Electron应用的时候,我就感觉他特别地令我感到熟悉。页面风格以及那令我熟悉的F12调试模式更是让我感到这种类型的应用似乎和之前我接触过的前端开发没有啥区别。下面我们来看下官方的解释:Electron 是一个开源的跨平台桌面应用开发框架,允许使用 JavaScript、HTML 和 CSS 构建兼容 Windows、macOS 和 Linux 系统的应用程序。它由 OpenJS Foundation 维护,核心原理是整合 Chromium 渲染引擎和 Node.js 运行时环境,为网页技术提供桌面运行时支持。现在我对于Electron的理解就是披着C/S外皮的前端应用。
该项目代码是我去年(2025年)在职的时候写的,当时可以正常运行测试。今年(2026年)在写博客的时候,由于已经离职,所以没有对应的Electron应用进行测试,项目的整体编写思路以及代码封装思路肯定是没有问题的,同学们可以根据实际的情况进行修改,请谅解。
一.言归正传,先来梳理一下Electron应用的自动化测试思路:
1.作为前端应用,想要进行自动化测试,必须使用到chrome浏览器的驱动
2.需要选择一门编程语言,我这里使用的是Python
3.需要选择一个自动化测试的框架,我这里使用的是Selenium
4.需要使用主流的PO架构,方便后期迭代更新
5.代码内部需要对组件的寻找方式进行封装,方便后期的迭代
二.明确了思路接下来进行代码解析:
1.需要确保Selenium 为4.x 版本
2.Python版本为3.9+版本
首先给大家看下代码的目录结构:

Constant目录下面存放的是静态变量,比如控件的id,chrome浏览器的驱动路径
driver目录下面存放的是Electron应用对应的的chrome浏览器的驱动程序
pages目录存放的是具体的页面操作逻辑代码
test目录下面存放的是测试主类,也就是自动化测试项目的入口
utils目录下面存放的是一些工具方法
接下来我们逐一看每个文件中的关键部分:
1.静态类Constant
python
# 常量类--后续按钮变动的时候,可以直接修改对应组件的XPath
# 配置常量
class Config:
chromedriver_path = r'..\driver\chromedriver-win64\chromedriver.exe'
yun_path = r"C:\Program Files\xxxx\CloudTest.exe" # 云安装路径
wait_for_scan = 15 # 等待扫码登录云的时间
class CloudElements: # 组件定义
ButtonLocator = '//*[@id="app"]/div/div[1]/span[1]' # 下拉列表按钮
ButtonWorkbench = '/html/body/div[3]/div/div/div/div/div/ul/li[4]' # 老师工作台按钮
ButtonQuestionBank = '//*[@id="app"]/div/div[2]/div[2]/div/div[17]' # 题库按钮
class WindowKeywords: #窗口关键字
workbench_name= '报表工作台'
具体的用法如下(注意:需要记得导包),这么做的原因也很简单,方便后续进行二次修改:
python
from Constant import constants
# 下拉列表按钮
self.button_locator = (By.XPATH, constants.CloudElements.ButtonLocator)
# 工作台按钮
self.button_workbench = (By.XPATH, constants.CloudElements.ButtonWorkbench)
# 题库按钮
self.button_question_bank = (By.XPATH, constants.CloudElements.ButtonQuestionBank)
2.utils工具类中,这里是整个Electron自动化测试的重中之重。主要提供了切换窗口的方法以及根据关键字查找窗口的方法,这么做的最主要的原因是:Electron切换窗口之后,无法自动找到对应的窗口,导致没有办法对页面中的元素进行查找操作。所以我们需要使用到下面两个方法,1.通过get_window_index方法传入的页面元素中的"关键字"查找到对应的window的index索引 2.通过switch_window方法切换到到对应的window窗口。(这里我一开始也是丈二的和尚摸不着头脑,在登录后进入主界面后,获取不到页面上的任何元素。最后还是使用AI工具了解到这里还有这么一层坑,感谢时代的发展)
python
# 根据关键字获取窗口索引
from selenium.webdriver.common.by import By
def get_window_index(self, target_text):
"""
查找窗口标题包含target_text的窗口索引
Args:
self: 测试类实例,包含driver属性
target_text (str): 要搜索的目标文本
Returns:
int: 包含目标文本的窗口索引,未找到返回-1
Description:
遍历所有窗口,检查每个窗口页面元素是否包含目标文本,
找到后返回该窗口在window_handles中的索引位置
"""
for index, handle in enumerate(self.driver.window_handles):
self.driver.switch_to.window(handle)
# 获取页面上所有元素
all_elements = self.driver.find_elements(By.TAG_NAME, "*")
for element in all_elements:
# print(element)
try:
# 获取元素文本内容
element_text = element.text
# 检查是否包含目标文本
if target_text in element_text:
# print(f"找到包含'{target_text}'的元素: {element_text}")
return index
except Exception as e:
# 忽略无法获取文本的元素
continue
return -1 # 未找到返回-1
# 切换窗口句柄
def switch_window(self, index):
"""
根据索引切换到指定浏览器窗口
Args:
self: 测试类实例,包含driver属性
index (int): 目标窗口在window_handles中的索引
Returns:
None: 无返回值
Description:
获取所有窗口句柄并切换到指定索引的窗口,
同时打印窗口句柄信息用于调试
"""
# 获取所有窗口句柄
window_handles = self.driver.window_handles
print(f"Window handles: {window_handles}")
print(f"Last window handle: {window_handles[-1]}")
# 切换到新的窗口句柄
self.driver.switch_to.window(window_handles[index])
3.现在介绍下主测试类
首先是setUp方法,在其中:1.进行了chrome浏览器驱动的初始化(需要找到当前Electron应用对应的chrome浏览器版本号,然后针对对应的浏览器版本号去下载对应版本的chrome浏览器驱动文件放到driver目录下面,点击跳转下载网页) 2.加载了Electron应用的exe路径 3.启动Electron应用
python
def setUp(self):
"""
测试初始化方法,在每个测试方法执行前运行
初始化Chrome浏览器驱动
"""
# 获取ChromeDriver路径
path = constants.Config.chromedriver_path
# 创建Chrome选项对象
chrome_options = Options()
# 设置浏览器二进制文件路径(云应用路径)
chrome_options.binary_location = constants.Config.yun_path
try:
# 创建Chrome浏览器实例,使用Service方式指定驱动路径
self.driver = webdriver.Chrome(service=Service(path), options=chrome_options)
except Exception as e:
# 当发生任何异常时执行的代码
print(f"发生异常:{e}")
其次,因为我们的Electron应用分为登录页面和主页面,所以我在初始化窗口的时候,先等待用户扫码登录,完成登录操作之后再去进行后续的自动化测试,代码如下所示:
python
def init_window(self): # 初始化窗口
"""
初始化窗口环境
创建CloudPage实例,等待用户扫码登录,并切换到工作台窗口
"""
# 创建CloudPage页面对象
self.Cloud_page = CloudPage(self.driver)
# Cloud_page.wait_for_page_load()
# 等待用户扫码登录,显示倒计时
for i in range(constants.Config.wait_for_scan):
print(f"请扫码登录桌面云,等待中... {i + 1}/{constants.Config.wait_for_scan}秒")
time.sleep(1)
# 切换窗口句柄到工作台
utils.switch_window(self, utils.get_window_index(self, constants.WindowKeywords.workbench_name))
# 打印当前浏览器地址
current_url = self.Cloud_page.get_current_url()
print(f"Current browser URL: {current_url}")
接着,我们来看下测试主类,在主类中:
1.我们首先调用init_window方法,初始化了窗口,并且在该方法中做了两件事:1.调用utils类中的get_window_index方法根据页面关键字,查找到工作台的窗口柄 2.调用utils类中的switch_window方法切换到查找到的句柄中句

2.调用Cloud_page中的点击按钮的方法,点击按钮跳转到工作台页面
3.然后点击资料库按钮,跳转到资料库页面
4.最后切换到资料库的句柄,切换方法和第1点中提到的方法一致,调用的是utils类中的get_window_index,switch_window方法。

5.最后点击资料库页面中的按钮,检测是否可以真正自动完成资料库页面的点击操作
python
def test_Cloud(self): # 测试实例
"""
主测试方法:测试云平台功能
包括:初始化窗口、跳转工作台、跳转资料库等操作
"""
# 初始化窗口环境
self.init_window()
print("跳转工作台...")
# 点击下拉列表按钮
self.Cloud_page.click_button()
print("点击工作台...")
time.sleep(2)
# 跳转到工作台页面
self.Cloud_page.jump_workbench()
print("跳转资料库...")
time.sleep(2)
# 跳转到资料库页面
self.Cloud_page.jump_question_bank()
time.sleep(20)
test主类的完整代码如下:
python
def test_Cloud(self): # 测试实例
"""
主测试方法:测试云平台功能
包括:初始化窗口、跳转工作台、跳转资料库等操作
"""
# 初始化窗口环境
self.init_window()
print("跳转工作台...")
# 点击下拉列表按钮
self.Cloud_page.click_button()
print("点击工作台...")
time.sleep(2)
# 跳转到工作台页面
self.Cloud_page.jump_workbench()
print("跳转资料库...")
time.sleep(2)
# 跳转到资料库页面
self.Cloud_page.jump_question_bank()
time.sleep(20)
4.最后介绍一下测试的业务主类,在该类中,封装了初始化以及点击的相关操作:
首先是初始化操作,通过XPATH的方式根据constants类中定义好的路径进行赋值:
python
def __init__(self, driver):
self.driver = driver
# 下拉列表按钮
self.button_locator = (By.XPATH, constants.CloudElements.ButtonLocator)
# 工作台按钮
self.button_workbench = (By.XPATH, constants.CloudElements.ButtonWorkbench)
# 资料库按钮
self.button_question_bank = (By.XPATH, constants.CloudElements.ButtonQuestionBank)
完整的代码如下:
python
# CloudPage.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from Constant import constants
class CloudPage: # CloudPage 工作台页面相关页面业务
def __init__(self, driver):
self.driver = driver
# 下拉列表按钮
self.button_locator = (By.XPATH, constants.CloudElements.ButtonLocator)
# 工作台按钮
self.button_workbench = (By.XPATH, constants.CloudElements.ButtonWorkbench)
# 资料库按钮
self.button_question_bank = (By.XPATH, constants.CloudElements.ButtonQuestionBank)
def jump_workbench(self, timeout=10): # 跳转工作台
button = WebDriverWait(self.driver, timeout).until(EC.element_to_be_clickable(self.button_workbench))
button.click()
def jump_question_bank(self, timeout=10): # 跳转资料库
button = WebDriverWait(self.driver, timeout).until(EC.element_to_be_clickable(self.button_question_bank))
button.click()
def wait_for_page_load(self, timeout=10): # 等待页面加载
WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(self.button_locator))
def click_button(self): # 点击编辑按钮
button = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable(self.button_locator))
button.click()
def get_current_url(self):
return self.driver.current_url
def get_elements_count(self):
elements = self.driver.find_elements(By.TAG_NAME, "*")
return len(elements)
完整的源码链接如下(收1个积分意思下,不喜勿喷,哈哈):