Selenium自动化测试:从核心原理到实战应用全解析

Selenium自动化测试:从核心原理到实战应用全解析 1. 项目概述为什么Selenium依然是自动化测试的基石如果你在软件测试或者爬虫开发领域待过一段时间Selenium这个名字绝对如雷贯耳。它不是一个新潮的工具但却是无数项目从零到一实现浏览器自动化的“老伙计”。我最早接触Selenium还是WebDriver刚出来那会儿用它来模拟用户操作处理那些需要登录、翻页的动态网页数据抓取。后来在测试团队它又成了我们保障Web应用质量的核心武器。这么多年过去了尽管有像Playwright、Cypress这样的后起之秀Selenium凭借其跨浏览器、多语言支持Java, Python, C#等和庞大的社区生态依然牢牢占据着企业级自动化测试和复杂场景模拟的头部位置。简单说Selenium就是一个让你用代码来遥控浏览器的工具。你想让浏览器自动打开某个网页、点击按钮、输入文字、下拉滚动条、截个图或者验证页面上某个元素是否存在这些原本需要手动重复的操作都可以交给Selenium脚本去完成。它的核心价值在于模拟真实用户行为这对于需要验证页面交互功能的测试或者获取JavaScript动态渲染数据的爬虫来说是不可替代的。很多人搜索“selenium爬虫”就是看中了它这一点——能搞定那些简单的requests库搞不定的、需要执行JS的网站。那么谁适合深入了解一下Selenium呢首先是测试工程师尤其是做Web UI自动化测试的这是基本功。其次是开发工程师你可能需要写一些集成测试用例或者构建端到端的验收测试。再者是数据工程师或分析师当你需要从一些复杂的、交互式的数据门户网站获取数据时Selenium往往是最后的“杀手锏”。当然也包括对自动化感兴趣的初学者用它来入门理解浏览器和脚本如何交互是个非常直观的选择。2. Selenium核心架构与组件拆解要玩转Selenium不能只停留在写几行find_element_by_id的层面理解它的“五脏六腑”至关重要。这能帮助你在遇到“selenium为什么没有调用浏览器”这类诡异问题时快速定位到是环境问题、驱动问题还是脚本逻辑问题。2.1 四层架构从你的代码到浏览器屏幕Selenium的工作流程可以抽象为一个四层架构理解这个就理解了它的全部。Selenium客户端库Client Libraries这就是你写的Python、Java或其它语言的代码。你调用的webdriver.Chrome()、find_element()、click()等方法都来自这一层。它提供了一套友好的API让你用编程的方式描述对浏览器的操作。JSON Wire Protocol过时/W3C WebDriver Protocol现行这是客户端和浏览器驱动之间的“通信语言”。你的API调用会被转换成标准的HTTP请求遵循W3C协议发送给浏览器驱动。这实现了客户端语言与浏览器驱动的解耦也是跨语言支持的基石。浏览器驱动Browser Drivers这是核心枢纽也是新手最容易踩坑的地方。每个浏览器Chrome、Firefox、Edge等都需要一个对应的驱动如chromedriver、geckodriver。驱动的作用是接收来自客户端的协议命令将其翻译成浏览器原生能理解的操作指令并控制真实的浏览器实例。很多人遇到的“selenium chromedriver 下载”和版本匹配问题就发生在这里。真实浏览器Real Browsers最终执行命令并渲染页面的“演员”。Selenium的强大就在于它控制的是Chrome、Firefox这些你日常在用的浏览器因此测试环境与真实用户环境高度一致。注意务必确保浏览器驱动如chromedriver的版本与本地安装的浏览器大版本号匹配。通常驱动版本号与浏览器主版本号一致即可。你可以通过浏览器的“关于”页面查看版本然后去官方仓库下载对应版本的驱动并将其所在目录添加到系统的PATH环境变量中。2.2 核心组件WebDriver、IDE与Grid我们常说的Selenium其实是一个项目集合主要包括三个组件应对不同场景Selenium WebDriver绝对的主力我们95%的时间都在和它打交道。它就是上面架构中提到的“客户端库协议驱动”的集合体提供了编程控制浏览器的能力。所有复杂的自动化逻辑都基于它构建。Selenium IDE一个浏览器插件主要用于Chrome和Firefox用于录制和回放浏览器操作。你手动操作一遍它能生成操作脚本。对于快速创建简单测试、探索性测试或者学习命令你搜索的“selenium ide命令”就是指这个非常有用。但它生成的脚本通常不够健壮难以维护复杂场景常用于原型制作或辅助脚本编写。Selenium Grid用于分布式测试。你可以在一台机器上控制多台远程机器的浏览器同时并行执行测试用例大大缩短测试总耗时。这对于需要覆盖多种浏览器、多种操作系统组合的兼容性测试矩阵来说是必备的基础设施。对于绝大多数个人学习和项目应用从Selenium WebDriver开始就足够了。IDE可以作为辅助Grid则在团队需要大规模并发执行时才会用到。3. 环境搭建与核心API实战理论说再多不如动手搭一个。这里我以最流行的Python语言和Chrome浏览器为例带你走一遍完整的流程并深入讲解几个最核心的API。3.1 环境准备与“Hello World”安装Selenium客户端库打开你的命令行执行pip install selenium。这是最简单的一步。下载浏览器驱动打开Chrome浏览器在地址栏输入chrome://settings/help查看版本比如 120.0.6099.217。然后访问Chromedriver的官方下载站或国内镜像站下载版本号对应的chromedriver。比如你的Chrome是120版本就下载主版本为120的chromedriver。放置驱动并配置路径有两种常用方式方式一推荐将下载的chromedriver.exeWindows或chromedriverMac/Linux文件直接放到Python解释器所在的目录或任何已存在于系统PATH环境变量的目录。你可以通过命令行输入python或where pythonWindows来查找这个目录。方式二在代码中指定驱动路径。更灵活尤其当你有多个项目或驱动版本时。现在写一个最简单的脚本hello_selenium.py来验证环境from selenium import webdriver from selenium.webdriver.common.by import By import time # 方式二的写法指定驱动绝对路径 # driver_path rC:\path\to\your\chromedriver.exe # Windows示例 # driver webdriver.Chrome(executable_pathdriver_path) # 旧版写法 # 新版Selenium通常只需如果驱动在PATH中 driver webdriver.Chrome() try: # 打开百度 driver.get(https://www.baidu.com) # 找到搜索框输入“Selenium” search_box driver.find_element(By.ID, kw) # 使用By来定位 search_box.send_keys(Selenium) # 找到“百度一下”按钮并点击 search_button driver.find_element(By.ID, su) search_button.click() # 等待2秒查看结果 time.sleep(2) finally: # 关闭浏览器 driver.quit()运行这个脚本你会看到一个Chrome浏览器自动打开访问百度输入关键词并搜索。恭喜你的Selenium世界大门已经打开。实操心得养成使用try...finally并在finally中调用driver.quit()的习惯。quit()会关闭所有窗口并终止WebDriver进程释放资源。而driver.close()只关闭当前标签页。资源泄露是自动化脚本常犯的错误之一。3.2 元素定位自动化测试的“眼睛”脚本要操作页面上的元素输入框、按钮、链接首先必须找到它。这就是“selenium定位”的核心。Selenium提供了8种主要的定位策略我将其分为“首选”、“备用”和“最后手段”三类定位方式By中的属性示例特点与建议IDBy.IDfind_element(By.ID, “kw”)首选。ID通常唯一定位最快、最准。NameBy.NAMEfind_element(By.NAME, “wd”)次选。Name也可能不唯一需确认。CSS SelectorBy.CSS_SELECTORfind_element(By.CSS_SELECTOR, “#kw”)功能强大首选之一。语法简洁支持大部分场景。XPathBy.XPATHfind_element(By.XPATH, ‘//input[id“kw”]’)功能最强大可遍历整个DOM。但性能稍差语法复杂。Class NameBy.CLASS_NAMEfind_element(By.CLASS_NAME, “s_ipt”)谨慎使用。Class常不唯一易变。Tag NameBy.TAG_NAMEfind_element(By.TAG_NAME, “input”)很少单独用通常结合其他条件。Link TextBy.LINK_TEXTfind_element(By.LINK_TEXT, “新闻”)仅用于精确匹配文本的链接a标签。Partial Link TextBy.PARTIAL_LINK_TEXTfind_element(By.PARTIAL_LINK_TEXT, “闻”)用于部分匹配链接文本。定位策略建议优先级IDNameCSS SelectorXPath。为什么推荐CSS Selector它比XPath更易读在大多数现代浏览器中解析速度更快。对于简单的属性选择如#id,.class,[name‘xxx’]CSS是首选。何时用XPath当元素没有ID/Name且CSS无法通过层级关系精确定位时。例如需要根据文本内容定位//button[text()‘提交’]或者需要复杂的轴定位如父节点、兄弟节点。绝对避免使用浏览器开发者工具直接复制的XPath它们往往是冗长且脆弱的“绝对路径”如/html/body/div[3]/div[2]/form/span/input页面结构稍有变动就会失效。应该学习编写简洁的“相对路径”XPath或使用CSS Selector。3.3 等待机制让脚本更“聪明”和稳定这是新手和老手的关键分水岭也是解决“元素找不到”错误的最重要工具。页面加载需要时间动态内容渲染也需要时间。硬编码time.sleep(10)是最糟糕的做法它要么浪费大量时间要么依然可能失败。Selenium提供了两种智能等待隐式等待和显式等待。1. 隐式等待 (Implicit Wait)在创建WebDriver后设置一个全局的等待时间。在这个时间内WebDriver会轮询DOM去寻找元素如果找到了就立即返回如果超时还没找到则抛出NoSuchElementException。driver webdriver.Chrome() driver.implicitly_wait(10) # 单位秒 # 后续所有的 find_element 操作都会最多等待10秒2. 显式等待 (Explicit Wait)针对某个特定的条件进行等待更加灵活和精确。这是推荐的最佳实践。你需要用到WebDriverWait和expected_conditions简称EC。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待直到某个元素可见、可点击 wait WebDriverWait(driver, 10) # 最长等10秒默认每0.5秒检查一次条件 element wait.until(EC.element_to_be_clickable((By.ID, “submit-btn”))) element.click() # 等待直到页面标题包含某个文字 wait.until(EC.title_contains(“订单提交成功”))显式等待的常见条件ECpresence_of_element_located: 元素出现在DOM中不一定可见。visibility_of_element_located: 元素可见宽高大于0。element_to_be_clickable: 元素可见且可点击。title_is,title_contains: 判断标题。alert_is_present: 判断是否有弹窗。如何选择混合使用以显式等待为主可以设置一个较短的全局隐式等待如5秒作为兜底。然后在关键交互点如点击按钮后等待新页面加载、等待弹窗、等待Ajax内容使用显式等待指定具体的条件。这样既保证了脚本的健壮性又避免了不必要的等待时间。彻底不用隐式等待一些严格的测试框架建议完全禁用隐式等待因为它在某些场景下可能与显式等待产生不可预知的交互。全部使用显式等待是更可控的做法。4. 高级技巧与复杂场景处理掌握了基本操作和等待就可以挑战更复杂的场景了比如处理iframe、弹窗、下拉框以及那个让人头疼的“滑块验证码”。4.1 处理特殊页面元素1. 下拉选择框 (Select)不要用click()去点选项Selenium提供了专门的Select类。from selenium.webdriver.support.ui import Select select_element driver.find_element(By.ID, “city”) select Select(select_element) # 三种选择方式 select.select_by_value(“beijing”) # 通过value属性 select.select_by_index(1) # 通过索引从0开始 select.select_by_visible_text(“北京市”) # 通过显示的文本 # 获取所有选项 all_options select.options for option in all_options: print(option.text)2. 框架/内嵌页面 (iframe)要操作iframe内的元素必须先“切换”进去。# 通过ID或Name切换 driver.switch_to.frame(“iframe_id”) # 或者通过元素定位切换 iframe driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe) # 操作iframe内的元素... driver.find_element(By.ID, “inner_button”).click() # 操作完成后切回主页面 driver.switch_to.default_content() # 或者切回上一级iframe # driver.switch_to.parent_frame()3. 弹窗 (Alert)处理浏览器的alert,confirm,prompt弹窗。# 触发一个alert driver.find_element(By.ID, “trigger-alert”).click() # 等待弹窗出现并切换到它 wait.until(EC.alert_is_present()) alert driver.switch_to.alert # 获取弹窗文本 print(alert.text) # 点击“接受”或“确定” alert.accept() # 点击“取消”或“拒绝” # alert.dismiss() # 如果是prompt可以输入文字 # alert.send_keys(“输入的文字”)4.2 模拟滑块验证码思路与局限“selenium模拟淘宝滑块”是一个高频搜索词这确实是一个经典且棘手的场景。完全模拟人类滑动成功率不高且容易被反爬机制识别这里主要分享思路和注意事项。核心思路是计算滑块需要滑动的距离然后模拟人类拖动轨迹。from selenium.webdriver import ActionChains import time # 1. 定位滑块和滑块背景图轨道 slider driver.find_element(By.ID, “slider”) track driver.find_element(By.ID, “track”) # 2. 计算滑动距离这是最难的部分 # 方法A如果缺口图是固定的可以预先计算像素差。 # 方法B更常见的是通过截图和图像识别库如OpenCV计算缺口位置。 # 这里假设我们已经通过某种方式得到了需要滑动的像素距离 distance distance 260 # 3. 模拟拖动 actions ActionChains(driver) actions.click_and_hold(slider) # 按住滑块 # 关键加入人类化的移动轨迹先快后慢加入抖动 actions.move_by_offset(distance * 0.3, 0) # 第一步快速移动30% time.sleep(0.1) actions.move_by_offset(distance * 0.4, random.randint(-2, 2)) # 第二步中速移动40%加入Y轴微小抖动 time.sleep(0.2) actions.move_by_offset(distance * 0.3, random.randint(-2, 2)) # 最后慢速移动30% actions.release() # 释放鼠标 actions.perform() # 执行动作链重要警告与局限性法律与道德风险此技术仅用于学习、测试自己拥有权限的网站。用于绕过他人网站的验证码获取数据可能违反网站服务条款甚至相关法律法规。技术对抗大型网站如淘宝的滑块验证码极其复杂包含轨迹识别、加速度检测、行为特征分析等。简单的模拟拖动几乎100%失败。图像识别难度大准确计算滑动距离需要处理带缺口的背景图和完整背景图涉及图像下载、灰度处理、边缘检测等代码复杂且不稳定。替代方案对于测试环境可以联系开发人员禁用验证码或提供万能验证码。对于爬虫应优先寻找是否有提供数据的官方API或者考虑使用更专业的反反爬服务成本较高。4.3 执行JavaScript与浏览器操作有些操作WebDriver API没有直接提供或者用JS更简单这时可以执行JavaScript。# 执行JS脚本 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 滚动到页面底部 driver.execute_script(“arguments[0].click();”, element) # 用JS点击元素可绕过某些前端拦截 driver.execute_script(“return document.title;”) # 获取返回值 # 浏览器导航 driver.back() # 后退 driver.forward() # 前进 driver.refresh() # 刷新 # 窗口和标签页 driver.get_window_handle() # 获取当前窗口句柄 driver.switch_to.window(handle) # 切换到指定窗口 driver.close() # 关闭当前标签页5. 测试框架集成与最佳实践单独写脚本跑没问题但要融入持续集成CI流程成为团队共享的资产就需要测试框架和良好的设计模式。5.1 与单元测试框架结合以pytest为例pytest是Python生态中最流行的测试框架之一与Selenium结合能很好地组织用例、生成报告。# test_baidu_search.py import pytest from selenium import webdriver from selenium.webdriver.common.by import By pytest.fixture(scope“function”) # 每个测试函数执行一次 def driver(): d webdriver.Chrome() d.implicitly_wait(5) yield d # 测试函数执行时使用这个driver d.quit() # 测试函数执行完毕后退出 def test_search_selenium(driver): driver.get(“https://www.baidu.com”) driver.find_element(By.ID, “kw”).send_keys(“Selenium”) driver.find_element(By.ID, “su”).click() assert “Selenium” in driver.title def test_search_pytest(driver): driver.get(“https://www.baidu.com”) driver.find_element(By.ID, “kw”).send_keys(“pytest”) driver.find_element(By.ID, “su”).click() # 使用显式等待等待结果出现 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC result WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.XPATH, “//div[id‘content_left’]”)) ) assert result is not None运行测试在命令行执行pytest test_baidu_search.py -v。pytest会自动发现并运行以test_开头的函数并使用driverfixture来管理浏览器的生命周期。5.2 Page Object Model (POM) 设计模式这是UI自动化测试的黄金标准核心思想是将页面对象和测试逻辑分离提高代码的可维护性和复用性。未使用POM的代码难以维护def test_login(): driver.find_element(By.ID, “username”).send_keys(“user”) driver.find_element(By.ID, “password”).send_keys(“pass”) driver.find_element(By.ID, “submit”).click()使用POM的代码创建页面对象类 (pages/login_page.py)from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LoginPage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 定位器 (Locators) USERNAME_INPUT (By.ID, “username”) PASSWORD_INPUT (By.ID, “password”) SUBMIT_BUTTON (By.ID, “submit”) ERROR_MSG (By.CLASS_NAME, “error”) # 页面操作方法 def enter_username(self, username): self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)).send_keys(username) return self # 支持链式调用 def enter_password(self, password): self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password) return self def click_submit(self): self.driver.find_element(*self.SUBMIT_BUTTON).click() return self def get_error_message(self): try: return self.driver.find_element(*self.ERROR_MSG).text except: return None在测试用例中使用页面对象 (tests/test_login.py)from pages.login_page import LoginPage def test_valid_login(driver): login_page LoginPage(driver) login_page.enter_username(“valid_user”) .enter_password(“valid_pass”) .click_submit() # 断言登录成功例如跳转到首页 assert “dashboard” in driver.current_url def test_invalid_login(driver): login_page LoginPage(driver) login_page.enter_username(“wrong”) .enter_password(“wrong”) .click_submit() error_text login_page.get_error_message() assert error_text “用户名或密码错误”POM的优势可维护性当页面元素ID变化时你只需要修改LoginPage类中的定位器所有测试用例无需改动。可读性测试用例读起来像自然语言业务逻辑清晰。复用性LoginPage类可以在多个测试用例中被复用。6. 常见问题排查与性能优化即使按照最佳实践来写脚本也难免会遇到问题。这里记录了一些我踩过的坑和解决方案。6.1 高频问题速查表问题现象可能原因排查步骤与解决方案NoSuchElementException(元素找不到)1. 元素尚未加载完成。2. 元素在iframe内。3. 定位器写错了。4. 页面有动态ID或Class。1.增加显式等待等待元素可见或可点击。2. 检查并切换到正确的iframe。3. 使用浏览器开发者工具F12的检查器Inspector重新确认定位器。4. 尝试使用更稳定的定位方式如XPath基于文本或相对路径。ElementNotInteractableException(元素不可交互)1. 元素被遮挡如弹窗、广告。2. 元素不可见style“display: none;”。3. 元素是disabled状态。1. 关闭遮挡物或等待其消失。2. 等待元素变为可见状态EC.visibility_of。3. 检查元素属性确认其disabled属性不为true。WebDriverException: unknown error: cannot find Chrome binaryChrome浏览器未安装或安装路径不在默认位置。1. 确认Chrome已安装。2. 在初始化WebDriver时指定浏览器路径options webdriver.ChromeOptions()options.binary_location r“C:\Custom\Path\chrome.exe”driver webdriver.Chrome(optionsoptions)SessionNotCreatedExceptionChrome浏览器版本与chromedriver驱动版本不匹配。这是最常见的问题检查并确保两者主版本号一致。下载对应版本的chromedriver。脚本运行速度慢1. 使用了过多的time.sleep()。2. 隐式等待时间设置过长。3. 网络或页面本身加载慢。1.用显式等待替代硬性等待。2. 合理设置隐式等待时间如3-5秒。3. 考虑使用无头Headless模式减少GUI渲染开销。浏览器被检测为自动化工具一些网站如某些登录页会检测navigator.webdriver属性。添加实验性选项来隐藏自动化特征但请注意这可能违反某些网站的使用政策options.add_experimental_option(“excludeSwitches”, [“enable-automation”])options.add_experimental_option(‘useAutomationExtension’, False)6.2 性能优化与高级配置使用无头模式 (Headless Mode)不显示浏览器GUI极大节省资源适合在服务器或CI环境中运行。from selenium.webdriver.chrome.options import Options options Options() options.add_argument(“--headless”) # 启用无头模式 options.add_argument(“--disable-gpu”) # 禁用GPU加速某些系统需要 options.add_argument(“--window-size1920,1080”) # 设置窗口大小即使在无头模式下 driver webdriver.Chrome(optionsoptions)禁用图片和CSS加载对于只关心数据和功能的测试可以禁用非必要资源加载大幅提升速度。prefs {“profile.managed_default_content_settings.images”: 2} # 2为禁用 options.add_experimental_option(“prefs”, prefs)复用浏览器会话对于需要登录的测试可以手动登录后保存cookies和本地存储在后续测试中复用避免每次重复登录。# 登录后保存cookies cookies driver.get_cookies() import json with open(“cookies.json”, “w”) as f: json.dump(cookies, f) # 新会话加载cookies driver.get(“https://example.com”) # 先访问域名 with open(“cookies.json”, “r”) as f: cookies json.load(f) for cookie in cookies: driver.add_cookie(cookie) driver.refresh() # 刷新页面使cookies生效7. Selenium vs. Playwright新老框架如何选“playwright和selenium优缺点”是现在很热的话题。作为两个都深度使用过的人我的看法是没有绝对的好坏只有适合与否。Selenium的优势生态成熟社区庞大出现早有海量的资料、问答、第三方工具和云服务集成如BrowserStack, SauceLabs。多语言支持官方支持Java, Python, C#, JavaScript, Ruby等适合不同技术栈的团队。真正的跨浏览器通过WebDriver协议控制真实的浏览器测试环境与用户环境一致。企业级认可度高在传统企业和大型项目中根基深厚。Selenium的劣势配置繁琐需要单独下载和管理浏览器驱动版本匹配是永恒的痛。API有时不够直观某些复杂操作如文件上传、拖拽的API较为笨拙。执行速度相对较慢基于HTTP协议通信有一定开销。对现代Web技术的原生支持需要额外配置来处理如Shadow DOM等。Playwright的优势开箱即用配置简单安装Playwright时会自动下载所有需要的浏览器Chromium, Firefox, WebKit无需单独管理驱动。API设计现代且强大自动等待、网络拦截、移动端模拟、录制工具等特性集成得非常好开发者体验更佳。执行速度快使用开发者工具协议CDP等与浏览器通信效率更高。对现代Web支持更好原生支持Shadow DOM、地理位置模拟等。Playwright的劣势生态相对较新虽然发展迅猛但社区资源和第三方集成相比Selenium仍有差距。浏览器版本受控它自带的是特定版本的Chromium/Firefox虽然稳定但测试的浏览器版本可能不是用户实际使用的不过它也支持连接已安装的Chrome/Edge。多语言支持虽然也支持多语言但其核心团队和最强生态在Node.jsJavaScript/TypeScript上。选择建议新项目且团队技术栈偏向Node.js/TypeScript强烈建议尝试Playwright它的开发体验和生产效率提升非常明显。已有成熟的Selenium项目或团队熟悉Java/Python继续使用Selenium是稳妥的选择迁移成本高且Selenium完全能满足需求。需要测试Safari浏览器或非常老的浏览器版本Selenium的Grid方案可能更成熟。Playwright对WebKitSafari内核的支持也很好但对旧版IE等无能为力。初学者学习两者都可以。Selenium资料多原理更透明Playwright上手更快挫折感更少。从理解原理角度Selenium仍是经典。我个人在实际工作中的体会是对于全新的自动化测试项目我会更倾向于推荐Playwright尤其是在追求开发效率和现代Web应用测试的场景下。但对于维护历史项目或者需要深度定制、对接复杂企业级测试平台的情况Selenium凭借其稳定性和灵活性依然是不可动摇的基石。工具终究是工具理解自动化测试的思想和设计模式如POM比纠结于具体框架更为重要。掌握了Selenium的核心再学习Playwright或其他框架都会触类旁通。