校园论坛系统Selenium自动化测试
本文为自动化测试
本项目自动化测试代码链接(仅供参考): 自动化测试代码
功能测试文章链接: 校园论坛系统自动化测试报告-CSDN博客
🌈自动化测试
思维导图
根据思维导图, 我们选取几个主要的功能进行自动化测试
编写代码
思路:
- 根据脑图进行测试用例的编写:每个页面一个测试类,然后再各个测试类中进行测试用例的编写
- 注意公共属性需要单独放一个类,方便进行代码复用
- 使用测试套件便于运行以及修改
- 创建启动以及现场截图就是会频繁进行复用,所以单独创建一个类进行存储
- 注意添加隐式等待,为了确保页面正确加载显示
💨准备工作
引入相关依赖
<!-- 屏幕截图 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <!-- 驱动管理 --> <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>5.8.0</version> </dependency> <!-- selenium --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.0.0</version> </dependency>
⭐创建常用工具类(AutoTestUtil)
工具类主要包含两个方法:
1. 创建驱动对象: 测试过程会频繁创建和销毁, 可以封装成一个方法, 方便使用
2. 屏幕截图: 测试过程可能会出现问题, 可以使用屏幕截图保存问题界面, 便于后面分析要求: 图片文件名要做到: 见文知意, 方便问题追溯, 具体到 年月日时分秒, 以及方法名
3. 获取当前方法名: 通过跟踪堆栈信息获取调用者的方法名
下面是参考代码:
public class AutoTestUtil { public static ChromeDriver driver = null; // 创建谷歌驱动对象 public static ChromeDriver createDriverUtil() { // 驱动打开谷歌浏览器 WebDriverManager.chromedriver().setup(); // 配置安全策略 ChromeOptions options = new ChromeOptions(); options.addArguments("--remote-allow-origins=*"); // 无头模式, 可以后台执行, 观察效果就关闭 // options.addArguments("--headless=new"); // 将配置添加到浏览器对象中 if (driver == null) { driver = new ChromeDriver(options); } // 添加 隐式等待 确保页面元素渲染出来 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); return driver; } // 屏幕截图: 文件名带有类名和方法名 public static void getScreenshotAs(String classMethodName) throws IOException { // 1. 用一个单独文件夹存放截图文件 // 2. 出问题能够快速定位到(方便查找, 见名知意) SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat hms = new SimpleDateFormat("HH_mm_ss_SS"); // HH:mm:ss 文件名字不能带有 :, 所以使用 _ String currentYMD = ymd.format(System.currentTimeMillis()); String currentHMS = hms.format(System.currentTimeMillis()); // 截图存储的路径 String filePath = "./src/test/image/" + currentYMD + "/" + classMethodName + "_" +currentHMS + ".png"; // 截图 File screenshotAs = ( (TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); File destFile = new File(filePath); // 保存到指定目录 FileUtils.copyFile(screenshotAs, destFile); } // 获取调用者方法名 public static String currentMethodName() { // 通过跟踪堆栈信息获取方法名 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // 索引选择规则: // stackTrace[0] -> getStackTrace() 方法本身 // stackTrace[1] -> 当前方法(currentMethodName) // stackTrace[2] -> 调用者方法(比如main) return stackTrace[2].getMethodName(); } }
每个页面的测试方法都使用一个类存放
⭐测试注册界面(ForumRegisterTest)
① 创建驱动,并打开注册页面
② 测试页面是否正常打开
③ 测试正常注册:多参数测试
④ 测试异常注册:密码和确认密码不一致的情况(此处不测null)
⑤ 注意测试方法的执行顺序,使用Order注解指定执行顺序
✅验证注册成功的情况
场景1:注册用户名在数据库不存在,昵称,两次密码一致,注册成功,跳转到登录界面
☑️预期结果: 注册成功, 跳转到登录界面
代码如下:
@Order(1) @ParameterizedTest @CsvSource({"Shylock, 夏洛克, 123456, 123456"}) void register(String username, String nickname, String password, String passwordRepeat) throws InterruptedException, IOException { driver.get("http://127.0.0.1:11451/sign-up.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#nickname")).sendKeys(nickname); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys(passwordRepeat); // 点击同意用户协议 driver.findElement(By.cssSelector("#policy")).click(); // 点击注册 driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
✅实际结果: 与预期结果相符
❌验证注册失败的情况
场景2:注册用户名在数据库存在,昵称,两次密码一致,注册失败
☑️预期结果: 提示当前用户已注册
代码如下: 测试代码和注册成功的代码一样
✅实际结果: 与预期结果相符
⭐测试登录界面(ForumLoginTest)
测试点:
① 创建驱动,并打开页面
② 测试页面是否正常打开
③ 测试正常登录:多参数测试
④ 测试异常登录:用户名/密码错误的情况(此处不测null)
⑤ 注意测试的顺序,使用Order注解指定,否则可能会因为执行顺序不对导致测试失败
⑥ 注意清空内容后才能再次输入用户名以及密码
✅登录成功测试
场景1:测试用户名正确,密码正确,登录成功(多个用户)
☑️预期效果: 两个用户都登录成功
代码如下:
@Order(1) @ParameterizedTest @CsvSource({"zhangsan, 123456", "2, 2"}) void login(String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); // 截图, 查看是否登录成功, 等待一秒, 防止页面没加载出来就截图了 Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
✅实际效果: 与预期效果相符
❌登录失败测试
场景2:测试用户名正确,密码错误,登录失败
测试用户名错误,密码错误,登录失败
☑️预期结果: 提示用户名或者密码错误和用户不存在
代码如下:
@Order(2) @ParameterizedTest @CsvSource({"zhangsan, 12345", "error1, error2"}) void noLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
✅实际结果: 与预期结果相符
⭐测试论坛主页(ForumIndexTest)
① 测试论坛主页是否可以正常打开
② 测试未登录的直接链接是否会跳转到登录页面
③ 查看“首页”元素是否存在,查看是否含有“发布帖子”按钮
④ 同样注意执行顺序
❌未登录状态下测试
场景1:未登录状态下进入论坛主页,直接跳转回登录界面
☑️预期结果: 直接跳转回登录界面
✅实际结果: 与预期结果相符
代码如下:
// 未登录状态访问论坛主页 @Order(1) @Test void noLoginVisitIndex() { driver.get("http://127.0.0.1:11451/index.html"); }
✅登录状态下测试
场景2:登录后进入博客列表页,查看“首页”元素是否存在,查看是否含有“发布帖子”按钮
☑️预期结果: 在论坛主页可以找到对应的选择器或XPath, 点击发布贴子会跳转到贴子创作页
代码如下:
@Order(2) @ParameterizedTest @CsvSource({"zhangsan, 123456"}) void forumIndexByLogin (String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); String str1 = driver.findElement(By.cssSelector("#nav_board_index > a > span.nav-link-title")).getText(); // 断言 "首页" 元素存在 Assertions.assertEquals("首页", str1); // 发布按钮, 点击会跳转到贴子发布页面 driver.findElement( By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")) .click(); // 截图, 查看是否跳转成功, 等待一秒, 防止页面没加载出来就截图了 Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
✅实际结果: 断言通过, 也找到了发布按钮
⭐测试发布帖子(writePostTest)
测试要点:
① 测试发布页是否可以正确打开
② 测试是否可以正常发布:元素齐全 or 部分元素
③ 测试“发布”按钮是否可以正常使用
④ 执行顺序
❌未登录状态下测试
场景1:未登录状态下发布帖子,直接跳转回登录界面
和前面类似, 但是测试用例不能少, 由于篇幅限制就不多演示了
☑️预期结果: 直接跳转回登录界面
✅实际结果: 与预期结果相符
✅登录后测试
场景2:登录后,输入帖子标题,不输入帖子内容,点击发布按钮,给出提示信息
☑️预期结果: 提示: 请输入贴子内容
代码如下:
@Order(2) @ParameterizedTest @CsvSource({"zhangsan, 123456"}) void writeActicleFailByLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement( By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")) .click(); String str1 = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div.card-body > div:nth-child(1) > label > strong")).getText(); // 断言判断是否处于发布贴子的页面 Assertions.assertEquals("版块", str1); // 输入标题 driver.findElement(By.cssSelector("#article_post_title")).sendKeys("标题"); // 不输入内容, 直接发布, 查看是否提示: 请输入文章内容 // 由于发布按钮需要向下滚动才能看到, 显示等待发布按钮加载 WebElement element = driver.findElement(By.cssSelector("#article_post_submit")); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); // 将指定元素滑动到页面顶部 ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView();", element); // 确保滚动成功 Thread.sleep(1000); element.click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
✅实际结果: 与预期结果相符
场景三: 登录后,输入帖子标题,输入帖子内容,点击发布按钮,发布成功
☑️预期结果: 跳转回论坛主页, 且在上方显示贴子信息
✅实际结果: 与预期结果相符
代码直接在前面的测试的方法上加上输入文本的操作即可
注意: editor编辑器不能通过选择选择到, 可以使用 键盘Tab 进行选择, 然后sendKeys即可
⭐测试贴子详情页(ActicleDetailTest)
① 测试详情页的正确打开
② 测试作者和用户权限是否一致③ 功能是否正常
④ 执行顺序
⑤ 一定要注意导航回到列表页的操作
❌未登录状态下
场景1:未登录状态下,进入贴子详情页,直接跳转回登录界面
☑️预期结果: 直接跳转回登录界面
✅实际结果: 与预期结果相符
和前面类似, 但是测试用例不能少, 由于篇幅限制就不多演示了
✅登录状态下
页面基础元素: 贴子标题、贴子内容、贴子作者、点赞按钮
特殊元素: 如果是作者, 会多显示 编辑按钮 和 删除按钮
场景1:登录后, 进入贴子详情界面, 且用户为贴子作者
☑️预期结果: 多显示 编辑按钮 和 删除按钮
✅实际结果: 与预期结果相符
场景2:登录后, 进入贴子详情界面, 用户不为贴子作者
☑️预期结果: 只显示 贴子标题、贴子内容、贴子作者、点赞按钮
✅实际结果: 与预期结果相符
两个用例一块测试了
场景1执行后的页面
场景2执行后的页面
测试代码如下:
@Order(2) @ParameterizedTest @CsvSource({"2, 2", "zhangsan, 123456"}) void acticleDetailByLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); // 断言进入到首页 Assertions.assertEquals(driver.findElement(By.cssSelector("#article_list_board_title")) .getText(), "首页"); // 进入到第一个贴子,如果页面上存在多个匹配相同定位条件的元素, 会默认返回第一个 WebElement element = driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong")); element.click(); // 断言进入了文章详情页面 element = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div:nth-child(2) > h3")); Assertions.assertEquals(element.getText(), "最新回复"); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
⭐测试删除贴子
❌未登录测试
场景1: 未登录状态下,点击删除
☑️预期结果: 直接跳转回登录界面
✅实际结果: 与预期结果相符
和前面类似, 但是测试用例不能少, 由于篇幅限制就不多演示了
登录状态下测试
场景1:登录后,进入帖子详情页,点击删除按钮,删除帖子成功,成功跳转回帖子列表页面
前提是当前登录用户为贴子作者
☑️预期结果: 提示删除成功
✅实际结果: 与预期结果相符
测试代码如下: 在上一个测试用例后面加上这串代码即可
element = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div:nth-child(1) > div.col-9.card.card-lg > div.card-footer.bg-transparent.mt-auto.justify-content-end > div > div:nth-child(3) > div > a > span")); // 断言当前用户是作者 Assertions.assertEquals(element.getText(), "删除"); element.click(); driver.findElement(By.cssSelector("#details_artile_delete")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName());
⭐测试所有帖子页面
❌未登录测试
场景1: 未登录状态下,点击我的帖子
☑️预期结果: 直接跳转回登录界面
✅实际结果: 与预期结果相符
和前面类似, 但是测试用例不能少, 由于篇幅限制就不多演示了
✅登录状态下测试
场景2:登录状态下, 点击我的帖子
☑️预期结果: 跳转到所有贴子页面
✅实际结果: 与预期结果相符
测试代码如下:
@Order(2) @ParameterizedTest @CsvSource({"zhangsan, 123456"}) void myAllActicleListByLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement(By.xpath("/html/body/div[1]/header[1]/div/div/div[3]/a")).click(); driver.findElement(By.cssSelector("#index_user_profile")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
⭐测试个人信息页面
❌未登录测试
场景1: 未登录状态下,点击个人中心
☑️预期结果: 直接跳转回登录界面
✅实际结果: 与预期结果相符
和前面类似, 但是测试用例不能少, 由于篇幅限制就不多演示了
✅登录状态显示个人信息测试
场景2:登录后点击用户头像下的个人中心,输入新昵称.....并修改,观察是否修改成功
☑️预期结果: 弹窗提示修改昵称成功
✅实际结果: 与预期结果相符
测试代码如下:
测试代码如下:
@Order(3) @ParameterizedTest @CsvSource({"zhangsan, 123456, 头顶尖尖"}) void updateUserMessageByLogin(String username, String password, String nickName) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement(By.xpath("/html/body/div[1]/header[1]/div/div/div[3]/a")).click(); driver.findElement(By.cssSelector("#index_user_settings")).click(); // 先清空 driver.findElement(By.cssSelector("#setting_input_nickname")).clear(); driver.findElement(By.cssSelector("#setting_input_nickname")).sendKeys(nickName); driver.findElement(By.cssSelector("#setting_submit_nickname")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
场景3:登录后点击用户头像下的个人中心,输入新密码并修改,其中两次密码一致,输入原密码也正确,观察是否跳转回登录界面
☑️预期结果: 跳转回登录界面, 强制重新登录
✅实际结果: 与预期结果相符
测试代码如下:
@Order(4) @ParameterizedTest @CsvSource({"zhangsan, 123456, 1234567, 123456"}) void updatePasswordByLogin(String username, String password, String newPassword, String repeatPassword) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement(By.xpath("/html/body/div[1]/header[1]/div/div/div[3]/a")).click(); driver.findElement(By.cssSelector("#index_user_settings")).click(); WebElement element = driver.findElement(By.cssSelector("#settings_submit_password")); // 滚动到下方 ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView();", element); // 确保滚动成功 Thread.sleep(1000); driver.findElement(By.cssSelector("#settings_input_oldPassword")).sendKeys(password); driver.findElement(By.cssSelector("#settings_input_newPassword")).sendKeys(newPassword); driver.findElement(By.cssSelector("#settings_input_passwordRepeat")).sendKeys(repeatPassword); driver.findElement(By.cssSelector("#settings_submit_password")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
⭐测试退出
场景1:登录后,点击用户头像下的退出按钮,跳转回登录界面,退出成功
最后释放驱动对象
☑️预期结果: 跳转回登录界面
✅实际结果: 与预期结果相符
测试代码:
@Order(4) @ParameterizedTest @CsvSource({"zhangsan, 123456"}) void exit(String username, String password) throws IOException, InterruptedException { driver.get("http://127.0.0.1:11451/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement(By.xpath("/html/body/div[1]/header[1]/div/div/div[3]/a")).click(); driver.findElement(By.cssSelector("#index_user_logout")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); // 最后释放驱动对象 driver.quit(); }
面试:
【面试】如何实现的,以及有什么亮点?
实现方式:
① 是根据个人项目来设计的测试用例,然后根据测试用例使用selenium4自动化测试工具
Junit5单元测试框架结合来实现web自动化测试的(功能、步骤、技术一定要明确)
② 对于代码中的每个包都要进行概要介绍(公共属性[复用]、测试用例[根据每个页面来进行设计的],然后使用测试套件将所有测试类进行加载)
亮点:
① 使用了JUnit5中提供的注解:避免生成过多的对象,造成资源和时间的浪费,提高了自动化的执行效率。
② 只创建一次驱动对象,避免每个用例重复创建驱动对象造成时间和资源的浪费。
③ 使用参数化:保持用例的简洁,提高代码的可读性
④ 使用测试套件:降低了测试人员的工作量,通过套件一次执行所有要运行的测试用例。
⑤ 使用了等待:提高了自动化的运行效率,提高了自动化的稳定性,减小误报的可能性。
⑥ 使用了屏幕截图:方便问题的追溯以及问题的解决。
⑦ 使用了无头模式:只注重结果,可以留出屏幕。
【自动化写出来不难,但是自动化执行速度快、更好地发现问题并避免误报才是难题。(一定要测试主要功能)】