
IDEA里搞定Selenium自动化测试其实没你想的那么玄乎做UI自动化测试的这些年我见到太多人一上来就纠结“该用哪个框架”“要不要上Docker”“是不是得搞个CI流水线”结果环境还没搭好就劝退了。实际上对一个刚开始接触自动化的团队或个人来说IDEA Java Selenium TestNG Maven这套组合已经足够你解决百分之八十的Web端回归测试需求也是目前招聘市场上出现频率最高的一套技能栈。这篇文章我不打算给你堆概念而是直接还原我在实际项目里从零搭建这套环境的完整过程。你会看到每个组件到底解决什么问题、为什么选它不选别的、pom.xml里每一行依赖该怎么写、下拉框里的组合该怎么优雅定位以及我踩过的那些坑。无论你是刚准备入门自动化测试的实习生还是被领导安排去搭框架的测试开发这套内容应该都能直接“抄作业”。1. 为什么偏偏是这套组合1.1 这套技术栈到底解决什么问题抛开各种花哨的“测试平台”“关键字驱动”“数据驱动”不谈UI自动化最朴素的需求只有三个用代码代替手工去点页面、用断言代替人眼去验结果、用报告代替截图去汇报工作。JavaSelenium负责第一件事TestNG负责第二和第三件事的框架支持Maven负责把所有依赖管起来。IDEA则是承载这一切的IDE它帮你写代码、跑测试、看报告少了哪一个都会让你在日常维护中多花不少冤枉时间。有人可能会问现在不是有Playwright、Cypress这种更新潮的工具吗为什么还在用Selenium我的看法是Selenium依然是兼容性最广的方案尤其是面对那些老旧的、基于传统服务端渲染的内部管理系统Selenium WebDriver的成熟度是其他工具比不了的。而且团队里招人会Selenium的人远多于会Playwright的人这就是现实。1.2 每个组件的角色定位简单做个类比如果自动化测试是一场演出Selenium是台上的演员负责执行动作TestNG是导演控制什么时候上场、什么时候谢幕、哪些环节要重点检查Maven是制片人帮你把道具、服装、剧本都准备齐统一放到后台IDEA就是排练厅你在里面编排一切还能随时看彩排效果。这个比喻听起来很粗浅但在跟业务方解释“你们到底在忙啥”的时候特别管用。回到技术层面这套组合里的每个角色都有更具体的分工下面我拆开详细说。2. 环境准备与工具版本搭配2.1 JDK、IDEA、Maven的安装细节工欲善其事必先利其器。先把三样基础工具装好JDK、IDEA、Maven。JDK建议直接用Java 8或Java 11这两个版本在Selenium和TestNG的兼容性上最稳。我自己目前在用的是JDK 8高版本的JDK比如17、21也能跑但有些老项目用了cglib代理或者低版本字节码库在高版本JDK下会出幺蛾子没必要为了追新给自己挖坑。IDEA选择社区版就完全够用了不需要破解什么激活码。社区版对Maven、TestNG、Gradle的支持都是完整的你只是写自动化测试脚本不是做Java EE企业级开发旗舰版里的Spring、Jakarta EE那些特性对你来说没什么用。Maven的话直接去官网下载二进制zip包解压到一个不带空格的路径下比如D:\dev\apache-maven-3.9.6。下载后别急着用去conf/settings.xml里配置阿里云镜像仓库不然你拉依赖的时候会被Maven中央仓库的龟速折磨到怀疑人生。配置代码很简单mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror2.2 Selenium、TestNG、Maven如何选版本版本选不对后面全是泪。Selenium目前主流的坐标是org.seleniumhq.selenium:selenium-java。4.x版本和3.x版本有一个很大的区别4.x内置了WebDriverManager的机制你不需要再单独引一个webdriver-manager依赖去管理驱动了当然单独引也不是不行看你的习惯。我推荐直接用Selenium 4.x的最新稳定版。以我写这篇文章时的最新版本为例在Maven仓库看到4.21.0或者更高都可以用。早期4.0到4.6之间的版本有一些API兼容问题反而没有3.141.59稳定但到了4.1x之后整体已经非常成熟了。TestNG的话7.x版本是目前的主流直接用最新的7.10.2或者7.9.0都行。Maven就用3.8.x或3.9.x这个影响不大只要能正常拉依赖就行。提示如果你用的是Selenium 4.6以上版本驱动管理可以完全交给Selenium自己搞定它会自动匹配浏览器版本并下载驱动。但如果是公司的内网环境外网受限那还是手动下载ChromeDriver放到指定位置更靠谱。3. Maven项目搭建与依赖管理3.1 在IDEA里创建Maven项目打开IDEA选择New Project左侧选MavenJDK选你配置好的Java版本。这里不需要勾选任何Maven模板直接Next就行因为Selenium自动化项目不需要webapp骨架只要一个普通的Java项目后续所有依赖都由pom.xml来声明。创建完成后IDEA会自动生成一个标准的Maven目录结构src main java test java pom.xml有一点比较重要我们在写自动化用例时测试代码放在src/test/java里不放main目录这是Maven约定的规范也是TestNG插件默认扫描的路径。有些人习惯把所有代码都扔到main里跑是能跑但后续如果接Maven Surefire插件做自动化回归就会出现测试无法被识别的问题。3.2 手把手配置pom.xml这是整个搭建过程中最关键的一步。打开pom.xml需要在dependencies节点里加入以下依赖dependencies !-- Selenium Java -- dependency groupIdorg.seleniumhq.selenium/groupId artifactIdselenium-java/artifactId version4.21.0/version /dependency !-- TestNG -- dependency groupIdorg.testng/groupId artifactIdtestng/artifactId version7.10.2/version scopetest/scope /dependency /dependencies如果这一步你用了Selenium 3.x那就还需要额外引一个webdriver-manager依赖比如io.github.bonigarcia:webdriver-manager:5.9.2。但既然能用4.x就不用给自己增加复杂度。依赖加好后IDEA右上角会弹出一个Maven刷新按钮点击刷新让IDEA去下载这些依赖。第一次下载会比较久因为selenium-java是个聚合依赖会连带拉下来一堆子模块比如selenium-api、selenium-chrome-driver、selenium-support等。看到进度条跑完之后在IDEA右侧的Maven面板里展开Dependencies确认没有红色的报错波浪线就说明依赖配好了。3.3 Maven到底在自动化测试里起什么作用再说说Maven本身。很多人学了半年Java面试被问到“Maven是干嘛的”还是支支吾吾。其实你完全可以这样理解Maven就是Java世界的App Store。你不需要自己下载jar包放进lib目录也不需要担心某个jar包依赖了另一个jar包但版本冲突了Maven会自动根据你声明的依赖去仓库里把所有需要的内容拉下来形成一个完整的依赖树。对自动化测试来说Maven还给了你一个额外的能力可以跳过IDE直接用命令行跑测试。比如你在项目根目录执行mvn clean test它会自动编译代码、执行所有TestNG用例然后生成测试报告。这就为以后接入Jenkins CI/CD留好了口子。4. 第一个TestNG测试用例从驱动配置到元素定位4.1 用代码实现浏览器启动环境全部准备好之后可以开始第一个用例了。先在src/test/java下建一个测试类比如叫FirstTest。这个类要做的第一件事就是打开浏览器访问一个网页验证标题是否正确。如果你是Selenium 4.6以上的版本直接这样写就行import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.testng.annotations.Test; public class FirstTest { Test public void testBaiduTitle() { WebDriver driver new ChromeDriver(); driver.get(https://www.baidu.com); String title driver.getTitle(); System.out.println(页面标题是 title); driver.quit(); } }注意这段代码看着简单但包含了一个很关键的约定用完driver一定要调用quit()而不是close()。close()只是关闭当前标签页如果浏览器弹了多个标签页进程不会被结束长期跑下来会产生大量僵尸进程把服务器的内存吃光。而quit()会关闭所有关联的页面并释放driver占用的系统资源。如果你用的是Selenium 4.6之前的版本或者公司网络访问不了Selenium的驱动下载地址那需要手动下载ChromeDriver并指定路径System.setProperty(webdriver.chrome.driver, D:\\drivers\\chromedriver.exe); WebDriver driver new ChromeDriver();4.2 TestNG注解到底应该怎么用TestNG最核心的东西就是注解。它的设计思路和JUnit很相似但功能更强大。自动化测试场景中最常用的几个注解包括Test标记一个方法为测试用例只有被这个注解标记的方法才会被TestNG执行。BeforeClass/AfterClass在类中所有测试用例执行前/后运行一次适合用来打开浏览器和关闭浏览器。BeforeMethod/AfterMethod在每一个测试用例执行前/后都运行一次适合用来做登录操作或清理测试数据。DataProvider数据驱动专用注解可以给同一个测试方法提供多组输入数据。很多初学者习惯每写一个用例都自己new一个driver然后在用例里又打开又关闭。这种做法在只有三五个用例的时候看不出问题一旦用例数量上到了100个你就知道了什么叫“打开浏览器就要等十秒”。正确的做法是使用BeforeClass统一初始化driver通过类级别的共享变量传给每个用例用例结束后统一在AfterClass里关闭。4.3 元素定位的实战选择定位元素是UI自动化的基本功。Selenium提供了定位方式有这么多种id、name、className、tagName、linkText、partialLinkText、xpath、cssSelector。我的建议是优先级从高到低这样排id name cssSelector xpath。原因是id和name在浏览器DOM中必须是唯一的定位效率最高cssSelector语法简洁且性能好xpath虽然功能最强但因为需要从根节点或全文档遍历执行速度相对较慢而且写复杂了以后维护成本很高。举例来说如果要定位一个登录按钮它的HTML可能是button idloginBtn classbtn-primary登 录/button那么可以这样写driver.findElement(By.id(loginBtn)).click();如果页面上没有id但class属性很独特可以写driver.findElement(By.cssSelector(button.btn-primary)).click();看起来很简单但实际操作中最大的问题是很多前端同学写的class是动态拼接的比如classel-button el-button--primary search-btn-19203后面的数字每次刷新都可能变。这种class就不能直接拿来用这时候不得不转头用xpath的相对定位或者用父级元素往下找子元素。定位策略的选择不能死板要根据页面的实际情况灵活调整。5. 非原生下拉框别再用select标签的思路去处理了5.1 为什么你定位不了那个“下拉框”在掌握基础定位之后很多人在实际项目中遇到的第一个真正的难题就是下拉框。如果你遇到的下拉框是原生HTML的select标签那Selenium提供了一个专门的处理方式Select select new Select(driver.findElement(By.id(city))); select.selectByVisibleText(北京);但问题来了现在前端框架Element UI、Ant Design、layui等渲染出来的下拉框基本上没有一个是原生的select标签而是用div、ul、li组合模拟出来的。你打开F12看根本找不到select这个节点。你用Select类去处理直接报UnexpectedTagNameException。这就是标题里提到“selenium定位获取下拉框元素不是原生下拉框是divulli组合”这个搜索热词的来源。遇到这种模拟下拉框你应该明白一个底层逻辑模拟下拉框本质上就是一个普通的HTML元素鼠标点一下它展开再点一下里面的某个选项就算选完整个过程跟下拉框本身的语义没有任何关系。所以处理思路其实就是两步展开下拉、点击选项。5.2 完整实战点击展开再精准选择目标项假设页面结构长这样div classcustom-select div classselect-trigger请选择城市/div ul classselect-dropdown styledisplay: none; li>// 第一步点击触发下拉框展开 driver.findElement(By.cssSelector(.select-trigger)).click(); // 第二步等待下拉列表渲染完成 Thread.sleep(1000); // 这种写法不够优雅但先能跑通 // 第三步在下拉列表里找一个包含目标文本的li元素并点击 driver.findElement(By.xpath(//ul[contains(class, select-dropdown)]/li[text()北京])).click();上面这套代码虽然能跑但有两点需要优化。第一Thread.sleep(1000)是硬等待不管页面响应快慢都要傻等一秒。如果网络慢一秒不够元素没出来就直接报错如果网络快这一秒纯属浪费。更好的做法是用Selenium的WebDriverWait做显式等待WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(10)); WebElement targetOption wait.until(ExpectedConditions.visibilityOfElementLocated( By.xpath(//ul[contains(class, select-dropdown)]/li[text()北京]) )); targetOption.click();第二xpath写得比较脆弱。如果前端对这个下拉框的类名稍微改一下用例就挂了。更稳妥的思路是先找到触发下拉的元素再基于这个元素找它的兄弟节点或父级节点里的目标li。在实际项目里我最常用的做法是给触发的div加一个自定义属性比如>!DOCTYPE suite SYSTEM https://testng.org/testng-1.0.dtd suite name自动化测试套件 test name登录模块测试 classes class namecom.example.tests.LoginTest/ class namecom.example.tests.CitySelectTest/ /classes /test /suite这里需要特别说明的是testng.xml的层级结构最外层是suite代表一个测试套件中间层是test代表套件中的一个测试场景你可以按模块划分也可以按执行的机器划分最里层是classes列出要执行的测试类。实际运行的时候你只需要在IDEA里右键testng.xml文件选择RunTestNG就会按照配置文件里声明的顺序去执行所有类。这也方便了你做分组冒烟测试一套xml完整回归又一套xml互不干扰。6.2 并行执行与线程安全TestNG支持并行执行用例配置方式很简单suite name自动化测试套件 parallelclasses thread-count3意思是最多同时开三个浏览器分别执行三个测试类。这样做的好处很明显执行时间大约能缩短到原来的三分之一。但代价是你的每个测试类必须保证线程安全不能用static变量去存driver否则不同线程之间会互相串数据。以我个人的经验刚开始搭框架的时候不要刻意追求并行。先把串行跑通了再考虑并行。因为并行带来的一系列随机问题比如WebDriver不是线程安全的、测试数据相互污染、浏览器资源竞争排查起来特别耗时容易打击团队信心。注意WebDriver实例本身不是线程安全的。如果要用并行执行最佳实践是用ThreadLocal把driver包装起来让每个线程持有自己的driver副本。不处理这一步就开parallel你会收到一堆莫名其妙的SessionNotFoundException。6.3 测试报告如何生成TestNG自带生成报告的能力。在执行完测试后到项目根目录的test-output目录下双击打开index.html就能看到一份结构完整的HTML报告每个测试方法的执行状态、耗时、失败信息的堆栈都会展示在上面。这份报告可以直接发给领导看不用自己再手工整理Excel。如果你觉得TestNG默认的报表面向开发人员业务方看不懂可以考虑引入testng-xslt或者用Allure Report来做美化。Allure需要额外装Allure Commandline工具并且要在pom.xml里添加Allure的适配器依赖。我个人的建议是先别上AllureTestNG自带报告你已经用一个月再说。很多人一上来就想搞“高级”结果光是环境配置就卡了一周真正的用例一个没写这就本末倒置了。7. 常见问题与排查技巧实录这一节我们直接进入“踩坑现场”。下面这些问题是我在实际项目里碰到过或者在帮别人解决环境问题时遇到最多的。7.1 浏览器启动闪退或报错ChromeDriver和浏览器版本不匹配是最常见的启动失败原因。Selenium 4.6以上版本虽然能自动匹配但如果你手动指定了旧版driver路径就会覆盖掉自动管理机制导致报错。排查的时候先看报错日志里是否有This version of ChromeDriver only supports Chrome version xx这句话。如果有去 Chrome for Testing 下载对应版本的driver就行。另外有一个比较容易忽略的情况如果你的Chrome浏览器安装了多个版本或者用了Chrome的Beta/Dev版本ChromeDriver会自动找默认的Chrome路径找不到就报cannot find Chrome binary。这种时候需要通过ChromeOptions显式指定浏览器路径比如ChromeOptions options new ChromeOptions(); options.setBinary(C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe); WebDriver driver new ChromeDriver(options);7.2 元素点击被拦截Web自动化里最常见的一个报错是ElementClickInterceptedException。字面意思是元素被其他东西挡住了。这种情况多发生在以下场景按钮上面浮着一个遮罩层、饼图/地图的canvas画布挡住了按钮、页面上弹出了非预期的toast提示、或者底部弹出了cookie同意框。我的排查习惯是先手工操作一遍业务看页面上到底有什么异常弹层。确认有遮挡后最粗暴也最有效的办法是在点击之前先让WebDriver按一下Esc键或者点击页面的空白区域把弹层关掉driver.findElement(By.tagName(body)).sendKeys(Keys.ESCAPE);有些弹层是异步出现的处理这种场景的关键不是等元素出现而是等遮挡元素消失。我会用这样的显式等待wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id(mask-layer)));这个思路可能很多人没有他们的习惯是等目标元素可点击可目标元素明明可点击却还是报拦截根本原因就是没等遮罩层消失。7.3 NoSuchElementException但元素明明在页面上这个问题恐怕是最让人抓狂的。你打开浏览器用F12明明看到了元素但脚本就是报找不到。出现这种情况通常不外乎四个原因。第一个原因是元素在iframe里。你在main页面里直接找iframe内部元素当然找不到。解决办法是先用driver.switchTo().frame()切换到对应的iframe操作完再切回主页面。第二个原因是元素在Shadow DOM里。这是一种Web Components技术的封装方式普通的findElement在默认情况下无法穿透Shadow DOM去定位内部元素。解决办法是用JavaScript执行器操作shadow root或者用Selenium 4提供的SearchContext接口去扩展。第三个原因是页面是异步渲染的你点击操作进行得太快。解决办法就是我在前面反复强调的用WebDriverWait做显式等待等元素出现再操作不要一上来就findElement。第四个原因比较冷门是页面有多个窗口/标签页WebDriver的焦点还停留在原来的窗口上。你需要用driver.getWindowHandles()获取所有句柄再switchTo()到正确窗口。7.4 隐式等待和显式等待到底怎么配合隐式等待和显式等待的配合问题是面试常客实际写代码时也经常有人搞混。隐式等待是对driver全局生效的设置一次之后每一次findElement都会在元素找不到时轮询等待一段时间。driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));显式等待是针对某一个特定条件生效的WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(10)); wait.until(ExpectedConditions.elementToBeClickable(By.id(submit)));这两种等待方式可以共存但要注意它们的时间是叠加的。如果隐式等待10秒显式等待也10秒最坏情况下一个元素要等20秒才会抛出异常。所以我的习惯是尽量用显式等待把隐式等待的时间设短一点比如5秒只作为兜底。这样可以兼顾稳定性和执行速度。7.5 测试数据如何清理自动化测试跑完以后环境里会留下很多脏数据比如测试创建的用户、测试提交的订单。如果不做清理第二次执行用例的时候重复的用户名会导致创建失败或者残留的订单会影响数据统计字段。最常用的方案有三种第一种是在BeforeMethod里通过调用后端接口或执行SQL去删除指定测试数据第二种是给每条测试数据加上固定前缀比如auto_test_user_20240101执行完用例后用脚本批量清理第三种是使用独立的测试环境跑完直接一键重置数据库。这三种方案里我最推荐的是第二种因为它改动最小不需要额外开发接口而且执行失败了也能通过命名规则手动定位到脏数据。8. 从能跑到会跑的三个进阶建议解决完上述问题你的自动化脚本基本就具备稳定的基础了。但如果想让这套框架在团队里真正跑起来还有三个进阶方向值得投入时间。第一个方向是数据驱动。把测试数据从代码里剥离出来放到Excel、JSON或者YAML文件里利用TestNG的DataProvider注解去循环执行用例。这样业务人员修改数据时不需要动代码只要你把数据文件维护好就行。第二个方向是日志系统。在每一个操作步骤里加上日志输出比如“点击登录按钮”“填写用户名”“断言登录成功”。日志是排查失败用例的第一手段尤其是当你执行的是几百条用例的回归集时一条清晰的操作日志能让你在十分钟内定位到问题而不是靠肉眼去看屏幕录像。第三个方向是把脚本交给CI/CD工具去定时跑。测试代码写得再牛如果只能依赖人手动在IDEA里点击运行那价值就大打折扣了。通过Maven命令行加上Jenkins或GitLab CI可以让代码在每天凌晨自动跑一遍第二天早上你打开邮箱看到报告就知道昨天的改动有没有破坏核心功能。这一步才是自动化测试真正的价值所在。我在实际项目里见过太多团队花了一个月搭框架、写脚本最后却坚持不下来本质原因不是技术不行而是用例维护成本太高。页面一改版定位器就全废了如果每个元素定位都散落在代码的各个角落改起来就特别痛苦。所以从第一天写代码起就一定要把元素定位集中管理。你可以用Page Object模式在类里维护该页面所有的元素定义页面结构变了只需要改一个类文件而不是全项目里全局搜索替换。另外一个实操小技巧定位元素时多跟前端开发沟通让他们在关键交互元素上加上稳定的id或>