基於Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一個WebUI自動化框架(1)搭建框架基本雛形


本次框架使用Maven作為代碼構建管理,引用了PO模式,將整體的代碼分成了頁面層、用例層、業務邏輯層。

框架搭建流程:

1、在pom.xml中引入依賴:

<!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
    <groupId>io.appium</groupId>
    <artifactId>java-client</artifactId>
    <version>7.0.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.0.0</version>
    <scope>test</scope>
</dependency>

<!--日志組件依賴-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2、(1)在resources目錄下添加好自己電腦瀏覽器對應版本的驅動chromedriver.exe,geckodriver.exe,IEDriverServer.exe;

驅動下載地址:https://npm.taobao.org/mirrors

(2)在resources目錄下再添加一個log4j.properties文件,用於配置日志的打印格式信息,添加如下信息:

#根logger主要定義log4j支持的日志級別及輸出目的地
log4j.rootLogger = DEBUG,console,file

###輸出信息到控制台配置###
#表示輸出到控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
#將System.out作為輸出
log4j.appender.console.Target = System.out
#使用靈活的布局展示日志信息
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#日志詳細輸出信息樣式
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

###輸出信息到文件中配置###
#每天產生一個日志文件
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
#輸出文件目的地
log4j.appender.file.File = log/web_auto.log
#新的日志信息是否追加到舊的日志文件末尾
log4j.appender.file.Append = true
#使用靈活的布局展示日志信息
log4j.appender.file.layout = org.apache.log4j.PatternLayout
#日志詳細輸出信息樣式
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

3、將頁面層的共性操作提取到common包下的BasePage類,將用例層的共性操作提取到common包下的BaseTest類。

package com.lrc.common;

import org.apache.log4j.Logger;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

/**
 * @param
 * @author lrc
 * @create 2021/12/13
 * @return
 * @description  封裝頁面層的公用方法
 **/
public class BasePage {
    private static Logger logger = Logger.getLogger(BasePage.class);
    /**
     * 顯式等待元素可見二次封裝
     * @param driver 驅動對象
     * @param by 元素定位信息
     */
    public WebElement waitElementVisible(RemoteWebDriver driver, By by ){
        WebElement webElement = null;
        try {
            //1、實例化WebDriverWait 超時時間10s
            WebDriverWait webDriverWait = new WebDriverWait(driver,10);
            //2、通過until方法等到某個條件滿足時為止
            webElement = webDriverWait.until(ExpectedConditions.visibilityOfElementLocated(by));
        }catch (Exception e){
            logger.error("定位元素【"+by+"】異常");
        }

        return webElement;
    }

    /**
     * 顯式等待元素可被點擊二次封裝
     * @param driver 驅動對象
     * @param by 元素定位信息
     */
    public WebElement waitElementClickable(RemoteWebDriver driver, By by ){
        WebElement webElement =null;
        try {
            //1、實例化WebDriverWait 超時時間10s
            WebDriverWait webDriverWait = new WebDriverWait(driver, 10);
            //2、通過until方法等到某個條件滿足時為止
            webElement = webDriverWait.until(ExpectedConditions.elementToBeClickable(by));
        }catch (Exception e){
            logger.error("定位元素【"+by+"】異常");
        }
        return webElement;
    }

    /**
     * 輸入框輸入數據通用方法
     * @param driver 驅動對象
     * @param by 元素定位信息
     * @param data 輸入的數據
     */
    public void sendKey(RemoteWebDriver driver, By by,String data,String elementName){
        logger.info("往元素【"+elementName+"】輸入數據【"+data+"】");
        waitElementVisible(driver,by).sendKeys(data);
    }

    /**
     * 點擊操作的通用方法
     * @param driver 驅動對象
     * @param by 元素定位信息
     */
    public void click(RemoteWebDriver driver, By by,String elementName){
        logger.info("對元素【"+elementName+"】進行點擊");
        waitElementClickable(driver,by).click();
    }


    /**
     * 獲取元素文本方法封裝
     * @param driver 驅動對象
     * @param by 元素定位信息
     * @param elementName 元素名稱
     * @return
     */
    public String getText(RemoteWebDriver driver,By by,String elementName){
        String text=waitElementVisible(driver,by).getText();
        logger.info("獲取元素【"+elementName+"】文本【"+text+"】");
        return text;
    }

    /**
     * 切換到指定IFrame封裝
     * @param driver 驅動對象
     * @param by 元素定位信息
     * @param frameInfo 自定義frame信息
     */
    public void switchFrame(RemoteWebDriver driver,By by,String frameInfo){
        WebElement element = waitElementVisible(driver, by);
        logger.info("切換IFrame:"+frameInfo);
        driver.switchTo().frame(element);
    }

    /**
     * 從IFrame中切換到默認頁面封裝
     * @param driver 驅動對象
     */
    public void switchDefaultFrame(RemoteWebDriver driver){
        logger.info("切換回默認的頁面");
        driver.switchTo().defaultContent();
    }

    /**
     * Alert彈窗切換
     * @param driver 驅動對象
     */
    public void switchAlert(RemoteWebDriver driver){
        logger.info("切換到alert窗口");
        Alert alert = driver.switchTo().alert();
        // alert.accept();  //點擊確定
        //alert.dismiss(); //點擊取消
        alert.getText();  //獲取彈窗文本
    }

}
package com.lrc.common;

import org.apache.log4j.Logger;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;

import java.util.Set;

/**
 * @param
 * @author lrc
 * @create 2021/12/13
 * @return
 * @description 封裝用例層的公用方法
 **/
public class BaseTest {
    private static Logger logger = Logger.getLogger(BaseTest.class);
    public RemoteWebDriver driver;

    /**
     * 打開所有瀏覽器通用方法封裝
     *
     * @param browserName 瀏覽器名
     */
    public void openBrowser(String browserName) {
        RemoteWebDriver webDriver = null;
        if ("chrome".equalsIgnoreCase(browserName)) {
            System.setProperty("webdriver.chrome.driver", "src\\test\\resources\\chromedriver.exe");
            webDriver = new ChromeDriver();
            logger.info("====================打開了chrome瀏覽器=====================");
        } else if ("firefox".equalsIgnoreCase(browserName)) {
            System.setProperty("webdriver.gecko.driver", "src\\test\\resources\\geckodriver.exe");
            webDriver = new FirefoxDriver();
            logger.info("====================打開了Firefox瀏覽器=====================");
        } else if ("ie".equalsIgnoreCase(browserName)) {
            DesiredCapabilities capabilities = new DesiredCapabilities();
            //取消IE安全設置(忽略IE的Protected Mode的設置)
            capabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
            //忽略瀏覽器縮放設置
            capabilities.setCapability(InternetExplorerDriver.IGNORE_ZOOM_SETTING, true);
            System.setProperty("webdriver.ie.driver", "src\\test\\resources\\IEDriverServer.exe");
            webDriver = new InternetExplorerDriver(capabilities);
            logger.info("====================打開了IE瀏覽器=====================");
        }
        driver= webDriver;
    }

    /**
     * 關閉瀏覽器通用方法
     */
    public void closeBrowser(){
        logger.info("====================關閉瀏覽器=====================");
        driver.close();
    }

    /**
     * 退出瀏覽器通用方法
     */
    public void quitBrowser(){
        logger.info("====================退出瀏覽器=====================");
        driver.quit();
    }



    /**
     * 最大化瀏覽器
     */
    public void maxBrowser(){
        logger.info("================最大化瀏覽器===================");
        driver.manage().window().maximize();
    }

    /**
     * 訪問指定網址
     * @param url 訪問地址
     */
    public void toURL(String url){
        logger.info("================訪問網址:==================="+url);
        driver.get(url);
    }


    /**
     * 封裝的通用切換窗口的方法-根據對應窗口的標題來切換
     * @param title 窗口標題
     */
    public void switchWindowWithTitle(String title){
        Set<String> allWindowHandles = driver.getWindowHandles();
        for (String windowHandle: allWindowHandles){
            //根據窗口的標題來進行判斷
            if(title.equals(driver.getTitle())){
                break;
            }else {
                logger.info("切換到標題為:【"+title+"】的窗口");
                driver.switchTo().window(windowHandle);
            }
        }
    }

    /**
     * 封裝的通用切換窗口的方法-根據對應窗口的url來切換
     * @param url 窗口url
     */
    public void switchWindowWithURL(String url){
        Set<String> allWindowHandles = driver.getWindowHandles();
        for (String windowHandle: allWindowHandles){
            //根據窗口的URL來進行判斷
            if (url.equals(driver.getCurrentUrl())){
                break;
            }else {
                logger.info("切換到url為:【"+url+"】的窗口");
                driver.switchTo().window(windowHandle);
            }
        }
    }



    public void myAssertTrue(boolean condition,String assertDescription){
        logger.info("斷言:【"+assertDescription+"】條件表達式【"+condition+"】");
        Assert.assertTrue(condition);
    }

    public void myAssertEquals(String actual,String expected,String assertDescription){
        logger.info("斷言:【"+assertDescription+"】實際值【"+actual+"】期望值【"+expected+"】");
        Assert.assertEquals(actual,expected);
    }

    public void myAssertEquals(int actual,int expected,String assertDescription){
        logger.info("斷言:【"+assertDescription+"】實際值【"+actual+"】期望值【"+expected+"】");
        Assert.assertEquals(actual,expected);
    }

    public void myAssertEquals(double actual,double expected,String assertDescription){
        logger.info("斷言:【"+assertDescription+"】實際值【"+actual+"】期望值【"+expected+"】");
        Assert.assertEquals(actual,expected);
    }

    public void myAssertEquals(float actual,float expected,String assertDescription){
        logger.info("斷言:【"+assertDescription+"】實際值【"+actual+"】期望值【"+expected+"】");
        Assert.assertEquals(actual,expected);
    }

    public void myAssertEquals(Object actual,Object expected,String assertDescription){
        logger.info("斷言:【"+assertDescription+"】實際值【"+actual+"】期望值【"+expected+"】");
        Assert.assertEquals(actual,expected);
    }
}

4、新建一個config包,新建一個全局配置數據類GlobalDatas,用於統籌管理項目的基礎信息。

package com.lrc.config;

/**
 * @param
 * @author lrc
 * @create 2021/12/13
 * @return
 * @description
 **/
public class GlobalDatas {
    //配置的瀏覽器
    public static final String BROWSER_NAME="chrome";
    //測試系統的登錄賬號
    public static final String USER_NAME="筱筱創";
    //測試系統的登錄密碼
    public static final String USER_PASSWORD="123456";
    //項目的URL地址
    public static final String INDEX_URL="https://www.baidu.com";
    //萬能驗證碼
    public static final String OMNIPOTENT_CODE="XXXX";
}

5、在page包下寫一個百度頁面的元素定位信息與操作方法:

package com.lrc.page;

import com.lrc.common.BasePage;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.RemoteWebDriver;

/**
 * @param
 * @author lrc
 * @create 2021/12/22
 * @return
 * @description
 **/
public class BaiduPage extends BasePage {
    private RemoteWebDriver driver;
    //搜索輸入框
    private By searchInputBy=By.id("kw");
    //百度一下按鈕
    private By searchSubmitBy=By.id("su");
    //新聞鏈接
    private By newsLinkBy=By.xpath("//a[text()='新聞']");
    //hao123鏈接
    private By hao123LinkBy=By.xpath("//a[text()='hao123']");
    //地圖鏈接
    private By mapLinkBy=By.xpath("//a[text()='地圖']");
    //貼吧鏈接
    private By tieBaLinkBy=By.xpath("//a[text()='貼吧']");
    //視頻鏈接
    private By videoLinkBy=By.xpath("//div[@id='s-top-left']/a[text()='視頻']");
    //圖片鏈接
    private By pictureLinkBy=By.xpath("//div[@id='s-top-left']/a[text()=' 圖片']");
    //網盤鏈接
    private By panLinkBy=By.xpath("//div[@id='s-top-left']/a[text()='網盤']");
    //更多鏈接
    private By moreLinkBy=By.xpath("//div[@class='mnav s-top-more-btn']/a[text()='更多']");

    //生成百度頁面的構造方法
    public BaiduPage(RemoteWebDriver driver) {
        this.driver = driver;
    }

    //在頁面層封裝向百度搜索框輸入數據的方法
    public void inputData(String data){
        sendKey(driver,searchInputBy, data,"百度搜索框");
    }

    //在頁面層封裝點擊【百度一下】的方法
    public void clickBaidu(){
        click(driver,searchSubmitBy,"百度一下按鈕");
    }

    //在頁面層封裝點擊"新聞"的方法
    public void clickNews(){
        click(driver,newsLinkBy,"新聞鏈接");
    }

    //在頁面層封裝點擊"hao123"的方法
    public void clickHao123(){
        click(driver,hao123LinkBy,"hao123鏈接");
    }
    //在頁面層封裝點擊"地圖"的方法
    public void clickMap(){
        click(driver,mapLinkBy,"地圖鏈接");
    }
    //在頁面層封裝點擊"貼吧"的方法
    public void clickTieBa(){
        click(driver,tieBaLinkBy,"貼吧鏈接");
    }
    //在頁面層封裝點擊"視頻"的方法
    public void clickVideo(){
        click(driver,videoLinkBy,"視頻鏈接");
    }
    //在頁面層封裝點擊"圖片"的方法
    public void clickPicture(){
        click(driver,pictureLinkBy,"圖片鏈接");
    }
    //在頁面層封裝點擊"網盤"的方法
    public void clickPan(){
        click(driver,panLinkBy,"網盤鏈接");
    }

}

6、在testcases用例層中編寫百度頁面的測試用例

package com.lrc.testcases;

import com.lrc.common.BaseTest;
import com.lrc.config.GlobalDatas;
import com.lrc.page.BaiduPage;
import org.testng.annotations.*;

/**
 * @param
 * @author lrc
 * @create 2021/12/22
 * @return
 * @description
 **/
public class TestBaidu extends BaseTest {
    @BeforeMethod
    public void setup(){
        //用例前置
        //1、打開瀏覽器
        openBrowser(GlobalDatas.BROWSER_NAME);
        maxBrowser();
        //2、進入登錄頁面
        toURL(GlobalDatas.INDEX_URL);
    }

    //測試百度搜索功能
    @Test
    public void test_baidu_success(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.inputData(GlobalDatas.USER_NAME);
        baiduPage.clickBaidu();
    }

    //測試點擊【新聞鏈接】
    @Test
    public void test_click_new(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickNews();
    }

    //測試點擊【hao123】
    @Test
    public void test_click_hao123(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickHao123();
    }

    //測試點擊【地圖】
    @Test
    public void test_click_map(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickMap();
    }

    //測試點擊【貼吧】
    @Test
    public void test_click_tieBa(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickTieBa();
    }

    //測試點擊【視頻】
    @Test
    public void test_click_video(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickVideo();
    }

    //測試點擊【圖片】
    @Test
    public void test_click_picture(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickPicture();
    }

    //測試點擊【網盤】
    @Test
    public void test_click_pan(){
        BaiduPage baiduPage=new BaiduPage(driver);
        baiduPage.clickPan();
    }


    @AfterMethod
    public void teardown(){
        //用例后置
        //退出瀏覽器
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        quitBrowser();
    }
}

 

至此,測試框架的基本雛形已經搭建好了,在TestBaidu測試類右擊執行即可運行測試用例了。

執行效果,由於用例執行的完整截屏文件太大,此處只截取前部分效果。

 

后續工作:

(1)實際復雜業務可能會涉及到很多個頁面,比如:商城下單業務,每個用例執行的時候都需要先進行登錄,選擇商品等操作,此處只是設計了頁面層、用例層,維護起來就顯得很乏力,因為這里每個用例都需要執行登錄,選擇商品等操作,非常繁瑣,為了解決這個問題,就需要添加個業務邏輯層,將很多用例都必須要執行到的共性操作封裝到這個層,在用例層只需要調用業務邏輯層的方法即可完成復雜業務,使得維護起來方便許多

(2)使用Testng提供的DataProvider實現數據驅動,使得測試數據與用例能夠解耦 

(3)引入Allure報表,統計用例執行情況

(4)引入失敗用例截圖與重試機制,提高代碼穩定性 

(5)引入並行測試機制,提高代碼執行效率

(6)配置GitLab/Github/SVN代碼倉庫,配置Jenkins拉取倉庫代碼,配置Jenkins自動化構建執行,輸出Allure報告,發送郵件/釘釘等


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM