目錄
1. 前言
2. 自動化實施步驟
3. 頁面分析
4. 設計測試用例
5. 搭建自動化環境
6. 編寫自動化代碼
6.1 準備工作 - Utils
6.1.1 允許遠程自動化 & 創建驅動
6.1.2?實現自動化截圖
?6.1.3?釋放 WebDriver
6.2 自動化測試登錄頁 - LoginTest
6.2.1 打開登陸頁
6.2.2?檢查登錄頁
6.2.3 測試登陸
6.2.3.1 登錄成功
6.2.3.2 登錄失敗
6.3 自動化測試列表頁 - ListPage
6.4 自動化測試詳情頁 - ?DetailPage
6.4.1 測試頁面內容
6.4.2 測試編輯按鈕功能
6.4.3 測試刪除按鈕功能
6.5 自動化測試編輯頁(發布博客) - EditPage
1. 前言
前段時間, 我們學習了 WebDriver 和 selenium, 并且掌握了自動化測試的核心函數.
此外, 我們基于 Spring 完成了博客系統的開發.
因此, 我們就使用自動化技術, 對我們的博客系統進行自動化測試.
2. 自動化實施步驟
- 需求分析
- 頁面分析
- 設計測試用例
- 搭建自動化環境
- 編寫自動化代碼
- 運行維護
設計測試用例時, 我們要根據項目中的頁面來設計對應的測試用例.
3. 頁面分析
博客系統一共分為以下 4 個頁面:
- 博客登錄頁
- 博客列表頁
- 博客詳情頁
- 博客發布(編輯)頁
我們需要對這些頁面來設計測試用例.
4. 設計測試用例
在編寫自動化代碼前, 需要先設計測試用例.
我們基于萬能公式設計測試用例:?
功能測試 + 性能測試 + 界面測試 + 兼容性測試 + 易用性測試 + 安全測試 (+ 弱網測試 + 安裝卸載測試)
除了牢記以上 “萬能公式” 外, 我們還需要充分的了解項目, 這樣才能設計出優秀的測試用例.
設計的測試用例如下:
5. 搭建自動化環境
設計完測試用例后, 需要為后續編寫自動化代碼搭建環境.
我們依賴如下環境:
- jdk
- idea
- selenium
- WedDriverManager(驅動管理)
- 瀏覽器(正版)
6. 編寫自動化代碼
根據測試用例, 編寫自動化代碼.
6.1 準備工作 - Utils
創建一個 Utils 包, 存放創建 driver, 截圖, 等待等操作的代碼.
6.1.1 允許遠程自動化 & 創建驅動
public WebDriver createDriver() {if(driver == null) {// 安裝自動化驅動管理程序WebDriverManager.chromedriver().setup();// 瀏覽器配置ChromeOptions options = new ChromeOptions();// 允許來自任何源的遠程連接請求options.addArguments("--remote-allow-origins=*");// 將配置添加到驅動中driver = new ChromeDriver(options);// 隱式等待頁面元素 3 s, 保證代碼執行前, 相關元素已存在.driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));}return driver;}
6.1.2?實現自動化截圖
?以截圖時間為所截圖片的保存路徑.
/*** 屏幕截圖* String s: 在進行哪個頁面的測試時, 進行的截圖.* 保存路徑: ./src/test/java/com/project/bloguiautotest/images/2025-05-03/15-44-23-23.png*/public static void screenShot(String s) throws IOException {// 年-月-日SimpleDateFormat dirFormat = new SimpleDateFormat("yyyy-MM-dd");// 時.分.秒.毫秒SimpleDateFormat fileFormat = new SimpleDateFormat("hh.mm.ss.SSS");String dirPath = dirFormat.format(System.currentTimeMillis());String filePath = fileFormat.format(System.currentTimeMillis()) + ".png";// 定義圖片保存路徑String picPath = "./src/test/java/com/project/bloguiautotest/images/" + dirPath + "/" + s + "-" + filePath;// 進行截圖, 生成圖片File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);// 將截圖保存在指定路徑下.FileUtils.copyFile(srcFile, new File(picPath));}
?6.1.3?釋放 WebDriver
/*** 釋放 driver 對象*/public static void exit() {driver.quit();}
6.2 自動化測試登錄頁 - LoginTest
對登錄頁的自動化測試流程如下:
- 使用 webDriver 打開登錄頁, 檢查頁面是否打開成功
- 針對登陸成功和登陸失敗的情況, 輸入賬號密碼, 檢查是否符合預期?
6.2.1 打開登陸頁
將 LoginTest 繼承 Utils, 將登錄頁的 url 傳給 Utils, driver.get(url) 打開博客登陸頁面.
6.2.2?檢查登錄頁
使用 driver.get 打開登陸頁后, 我們需要檢查是否打開成功.
6.2.3 測試登陸
登陸頁成功打開后, 接下來測試登錄狀態.
6.2.3.1 登錄成功
輸入賬號密碼后, 如果登錄成功, 會跳轉到博客詳情頁.
因此, 我們可以查找詳情頁中的元素, 如果可以查找到, 則說明登陸成功.
博客詳情頁中有很多元素, 這里我選擇的是 “退出登錄” 這個按鈕.
6.2.3.2 登錄失敗
輸入的賬號密碼屬于以下情況任意一種時, 均會登錄失敗:
- 賬號為空, 密碼不為空
- 賬號不為空, 密碼為空
- 賬號和密碼, 都為空
- 賬號正確, 密碼錯誤
- 賬號錯誤, 密碼正確
- 賬號錯誤, 密碼錯誤
- 輸入不合法字符
這里演示第 4 種情況.
當輸入密碼錯誤時, 會出現警告彈窗提示, 我們需要關閉彈窗:
注意:
如果對輸入框進行輸入操作(sendKeys), 一定要先清空輸入框(clear)中的內容!!
6.3 自動化測試列表頁 - ListPage
登陸成功后, 來到博客詳情頁.?
博客列表頁中有以下三部分模塊:
- 個人信息模塊
- 博客列表模塊
- 菜單欄模塊
這里就僅對博客列表模塊進行測試:
public class ListPage extends Utils {// 博客列表頁 urlpublic static String url = "http://47.93.87.16:8080/blog_list.html";public ListPage() {super(url);}/*** 測試博客列表頁* 1. 個人信息模塊* 2. 博客列表模塊 ?* 3. 菜單欄模塊*/public void checkList() {// 獲取博客標題String blogTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();// 獲取博客發布日期String blogDate = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText();// 獲取博客正文String blogContent = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.desc")).getText();// 獲取 “查看全文>>” 按鈕String button = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).getText();// 博客標題 & 博客發布日期 & 博客正文 均不能為空assert !blogTitle.isEmpty();assert !blogDate.isEmpty();assert !blogContent.isEmpty();// “查看全文>>” 按鈕應符合預期assert button.equals("查看全文>>");// 點擊 “查看全文>>” 按鈕, 應跳轉到博客詳情頁.driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();// 此時, 應來到博客詳情頁. 獲取詳情頁中的標題String jumpTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();// 跳轉后的博客標題, 應和跳轉后的保持一致assert blogTitle.equals(jumpTitle);// 此時已經來到博客詳情頁. 獲取這篇博客詳情頁的 URL.blogDetailURL = driver.getCurrentUrl();
// System.out.println(blogDetailURL);}
}
6.4 自動化測試詳情頁 - ?DetailPage
6.4.1 測試頁面內容
對博客詳情頁進行測試時, 和登錄頁/列表頁不同的是, 我們不能將一篇博客的詳情頁的?url 寫死到屬性中, 因為這篇博客隨時可能會被刪除, 此時這個 url 就變成了一個不存在的博客的 url.
因此, 我們可以在博客列表頁中獲取某一篇存在的博客, 對這篇博客的詳情頁進行測試.
注意:?由于博客正文內容使用的標簽不固定, 有時是 p 標簽, 有時是 h 標簽(動態標簽), 我們無法為其編寫一個固定的選擇器.
?解決方案是:利用其結構穩定的父元素. 先通過選擇器定位到穩定的父元素, 然后基于此父元素來定位其內部的動態正文內容.
博客正文, 都存在于一個靜態的 div 標簽中, 因此, 我們可以通過定位正文的父類即 div 標簽來間接的定位博客正文.
public class DetailPage extends Utils {// 獲取父類中有效的 urlpublic static String url = blogDetailURL;public DetailPage() {super(url);}/*** 測試詳情頁*/public void checkPage() {// 博客標題driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));// 博客發布日期driver.findElement(By.cssSelector("body > div.container > div.right > div > div.date"));// 博客正文// 正文 -》 動態標簽. 解決辦法: 父類標簽是靜態的, 通過父類標簽間接定位到博客正文的標簽.(確定了父類, 就確定了子類)
// driver.findElement(By.cssSelector("#detail > p"));webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#detail")));driver.findElement(By.cssSelector("#detail")); // 父類 div 標簽// 編輯按鈕// 刪除按鈕// 可能存在, 也可能不存在. 當作者是登陸用戶的時候存在, 因此這里不進行測試.}
}
6.4.2 測試編輯按鈕功能
我們的預期時, 在博客詳情頁點擊 "編輯" 按鈕, 會跳轉到博客編輯頁, 對博客信息進行修改, 點擊 "更新文章" 按鈕, 文章信息隨之發送改變.
我們編寫自動化代碼驗證功能是否正常.
/*** 檢測博客詳情頁 編輯按鈕 功能*/public void checkDetailEdit() throws InterruptedException {// 獲取修改前的標題String titleBefore = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();System.out.println("更新前的標題: " + titleBefore);// 查找 "編輯" 按鈕并點擊driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(1)")).click();// 此時跳轉到編輯頁, 更新標題 (首先清空之前的標題)webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#title")));driver.findElement(By.cssSelector("#title")).clear();// 根據當前時間更新標題webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#title")));SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HHmmssSS");String titleTime = simpleDateFormat.format(System.currentTimeMillis());driver.findElement(By.cssSelector("#title")).sendKeys(titleTime);//點擊發布----回到列表頁driver.findElement(By.cssSelector("#submit")).click();// 獲取更新后的標題 body > div.container > div.right > div:nth-child(1) > div.titlewebDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.container > div.right > div > div.title")));String titleAfter = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();System.out.println("更新后的標題: " + titleAfter);// titleBefore != titleAfter 更新前和更新后的標題, 應不一致.assert !titleAfter.equals(titleBefore);}
使斷言 assert 生效需要進行一下配置:
-ea -Dfile.encoding=UTF-8?
6.4.3 測試刪除按鈕功能
定位到博客詳情頁的刪除按鈕后進行點擊, 完成刪除操作.
如何驗證刪除操作成功: 博客列表頁中, 刪除操作前的博客數量應和刪除操作后的博客數量不同.
/*** 檢測博客詳情頁 刪除按鈕*/public void checkDetailDelete() throws InterruptedException {// 先回到列表頁, 確定刪除前, 一共有多少篇博客driver.get("http://47.93.87.16:8080/blog_list.html");List<WebElement> blogsBefore = driver.findElements(By.cssSelector("body > div.container > div.right > div"));int blogCntBefore = blogsBefore.size();System.out.println("刪除操作前的博客篇數: " + blogCntBefore);// 再次來到博客詳情頁, 定位到刪除按鈕并點擊driver.get(url);driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(2)")).click();// 此時來到博客列表頁, 重新查看共有多少篇博客Thread.sleep(500);List<WebElement> blogsAfter = driver.findElements(By.cssSelector("body > div.container > div.right > div"));int blogCntAfter = blogsAfter.size();System.out.println("刪除操作后的博客篇數: " + blogCntAfter);// 刪除前和刪除后, 列表頁的博客數量應不同assert blogCntBefore != blogCntAfter;}
6.5 自動化測試編輯頁(發布博客) - EditPage
通過 url 來到博客發布頁后, 我們需要填寫博客標題和博客正文.
填寫博客標題時, 我們通過選擇器獲取 xpath 定位到后, 直接通過 sendKeys 就可以自動化填寫.
但是, 填寫博客正文時, 如果我們仍然通過 sendKeys 來填寫內容時, 就會拋出元素不可交互異常:
ElementNotInteractableException?異常通常意味著我們通過 findElement 找到了指定的元素(findElement 成功), 但該元素當前狀態無法接收鍵盤輸入. (例如, 它可能被其他元素遮擋、被禁用、不可見, 或者它本身就不是一個設計用來直接接收輸入的元素, 如: 富文本編輯器)
而接收博客正文的區域, 它恰好是一個文本編輯器, 因此我們無法通過 sendKeys 來輸入內容.
此時, 我們就可以通過鍵盤鼠標操作來對向這個文本編輯器中輸入內容.
鍵盤操作 | Selenium
// 通過鍵盤鼠標的方式, 來填寫正文WebElement contentEle = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));new Actions(driver).doubleClick(contentEle) // 模擬鼠標雙擊操作.keyDown(Keys.DELETE) // 模擬鍵盤刪除操作.doubleClick() // 模擬鼠標雙擊操作.keyDown(Keys.DELETE) // 模擬鍵盤刪除操作.sendKeys("自動化填寫博客正文...") // 輸入內容.perform(); // 將結果展示到頁面上
ok, 自動化輸入完博客正文后, 就可以點擊 "發布博客" 按鈕進行發布了.
那么, 如何自動化測試博客發布成功呢?
點擊發布按鈕后, 此時應來到博客列表頁, 而博客列表頁的最后一篇博客就是我們自動化發布的博客.
那我們就可以獲取列表頁最后一篇博客的標題/內容, 檢查是否和自動化輸入的標題/內容一致, 若一致, 則說明發布成功, 反之發布失敗.
如何獲取選中列表頁最后一篇博客呢? 我們可以采取拼接選擇器/xpath的方式:
/*** 發布博客*/
public class EditPage extends Utils {private static final String url = "http://47.93.87.16:8080/blog_edit.html";public EditPage() {// 調用父類構造, 來到博客發布頁面super(url);}/*** 正常發布博客*/public void editSuccess() {// 1. 填寫博客標題// 根據當前時間自動化生成博客標題webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#title")));SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-HH:mm:ss-SS");String titleTime = simpleDateFormat.format(System.currentTimeMillis());String titleBefore = "自動化發布博客 " + titleTime;driver.findElement(By.cssSelector("#title")).clear();driver.findElement(By.cssSelector("#title")).sendKeys(titleBefore);System.out.println("發布時輸入的博客名稱: " + titleBefore);// 2. 填寫博客正文// 由于正文的輸入框是一個文本編輯器(不可交互), 如果通過 sendKeys 來進行輸入操作, 會拋出異常.
// // ElementNotInteractableException
// WebElement contentEle = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
// contentEle.sendKeys("自動化填寫博客正文...");// 通過鍵盤鼠標的方式, 來填寫正文WebElement contentEle = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));new Actions(driver).doubleClick(contentEle) // 模擬鼠標雙擊操作.keyDown(Keys.DELETE) // 模擬鍵盤刪除操作.doubleClick() // 模擬鼠標雙擊操作.keyDown(Keys.DELETE) // 模擬鍵盤刪除操作.sendKeys("自動化填寫博客正文...") // 輸入內容.perform(); // 將結果展示到頁面上// 3. 點擊發布博客driver.findElement(By.cssSelector("#submit")).click();// 4. 校驗博客是否發布成功// 此時來到博客列表頁// 博客列表頁的最后一篇博客, 應是我們新發布的博客, 這篇博客的名稱應和我們發布時輸入的名稱一致// 獲取列表頁的博客列表List<WebElement> blogs = driver.findElements(By.cssSelector("body > div.container > div.right > div"));int size = blogs.size();// 獲取最后一篇博客的名稱String lastBlogTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(" + size + ") > div.title")).getText();System.out.println("發布后的博客名稱: " + lastBlogTitle);assert lastBlogTitle.equals(titleBefore);}
}
END