Java Selenium框架的开发和优化教程-01

1.背景

在我给我们公司写的爬虫框架和UI自动化测试框架中,都使用到了Java+Selenium,而随着框架功能体积的增大,很自然地需要对Selenium进行进一步的封装和优化,以应对愈发复杂的需求。
下面我把这两个项目中,对于selenium的使用、封装和优化的方式抽出来写成一个Maven的demo项目,进行详细介绍,供大家参考。

2.项目一览

2.1 项目结构

这个demo的Maven项目是我精简过的,所以非常简单,结构如下图:
在这里插入图片描述
核心代码:
Selenium操作页面的流程是:初始化浏览器的driver,启动浏览器 -> 定位界面的元素 -> 操作界面元素。
所以我把这三大步分布封装成图中的三个类:

  • UiActions - 对UI基础操作的封装
  • UiContext - 对浏览器上下文的封装
  • UiFinder - 对UI元素查找器的封装

在下文中会对核心代码有详细的介绍。

具体实现
这里我以百度为例,简单演示了封装好的框架是如何操作浏览器并执行百度搜索的过程。

浏览器驱动
浏览器驱动我使用的是Chrome Driver,放在resources路径下是方便框架直接进行调用。

2.2 集成浏览器驱动WebDriver

首先,需要下载WebDriver。各浏览器下载地址:

Firefox浏览器驱动:geckodriver

Chrome浏览器驱动:chromedriver taobao备用地址

IE浏览器驱动:IEDriverServer

Edge浏览器驱动:MicrosoftWebDriver

Opera浏览器驱动:operadriver

PhantomJS浏览器驱动:phantomjs

打开taobao备用地址,我下载的是74.0.3729.6版本(当然你们也可以选择更新的版本),如下图:
在这里插入图片描述
然后下载chromedriver_win32.zip并解压,如下图:
在这里插入图片描述
然后将其中的chromedriver.exe放到项目的resource\WebDriver路径下即可。

2.3 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javaseleniumdemo</groupId>
    <artifactId>javaseleniumdemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.141.59</version>
        </dependency>

        <!--可以引入日志 @Slf4j注解-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <!--用于截屏保存文件-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
</project>

3.代码示例

3.1 对浏览器上下文的封装

UiContext这个类即为对浏览器上下文的封装,它需要包括初始化浏览器、设置浏览器特性、获取浏览器上下文以及关闭浏览器释放资源等功能,代码如下:

package com.javaseleniumdemo.core;

import org.openqa.selenium.UnexpectedAlertBehaviour;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.WebDriverWait;


/**
 * @author Joy
 */
class UiContext {

    private static UiContext uiContext;
    private static WebDriver driver;
    private static WebDriverWait wait ;

    /**
     * description: 获取UI上下文的driver
     * @return WebDriver
     */
    public static WebDriver getDriver() {
        WebDriver d = null;
        try {
            d = UiContext.getUiContext().driver;
        } catch (Exception e){
            e.printStackTrace();
        }
        return d;
    }

    /**
     * description: 获取UI上下文的driver等待时间
     * @return WebDriverWait
     */
    public static WebDriverWait getDriverWait() {
        WebDriverWait time = null;
        try {
            time = UiContext.getUiContext().wait;
        } catch (Exception e){
            e.printStackTrace();
        }
        return time;
    }


    /**
     * description: 用于初始化context
     * @return UiContext
     */
    public static UiContext initialUiContext() {
        try {
            if(uiContext == null){
                uiContext = new UiContext();
                System.setProperty("webdriver.chrome.driver", "src/Main/resources/WebDriver/chromedriver.exe");

                ChromeOptions chromeOptions = new ChromeOptions();

                // 当在服务器上运行时,需要使用此句将浏览器设为无头模式
//                chromeOptions.addArguments("--headless");

                DesiredCapabilities capabilities = DesiredCapabilities.chrome();
                capabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions);
                // 使浏览器可以处理alert
                capabilities.setCapability(CapabilityType.UNEXPECTED_ALERT_BEHAVIOUR, UnexpectedAlertBehaviour.IGNORE);

                uiContext.driver = new ChromeDriver(capabilities);
                // 使浏览器全屏
//                uiContext.driver.manage().window().fullscreen();
                uiContext.driver.manage().deleteAllCookies();
                uiContext.wait = new WebDriverWait(driver, 30, 1);
                driver.manage().window().maximize();

                Thread.sleep(2000);
                System.out.println("UiContext初始化成功,ChromeDriver创建成功。");
            }
        } catch (Exception e){
            System.out.println("UiContext初始化失败:" + e.getMessage());
            e.printStackTrace();
        }
        return uiContext;
    }

    /**
     * description: 获取浏览器上下文
     * @return UiContext
     */
    public static UiContext getUiContext() {
        if(uiContext == null){
            initialUiContext();
        }
        return uiContext;
    }

    /**
     * description: 浏览器退出并释放资源
     */
    public static void dispose() {
        if (driver != null){
            driver.quit();
            driver = null;
            uiContext = null;
        }
    }

    /**
     * description: 浏览器关闭
     */
    public static void close(){
        if (driver != null){
            driver.close();
        }
    }
}

3.2 对UI元素查找器的封装

UiFinder这个类即为UI元素查找器的封装。了解Selenium的同学们都知道,Selenium在查找界面元素的时候都是通过特定的selector来定位的,它提供了8种selector:

  • id
  • name
  • class name
  • tag name
  • link text
  • partial link text
  • xpath
  • css selector

这8种定位方式在selenium中所对应的方法为:

  • findElement(By.id())
  • findElement(By.name())
  • findElement(By.className())
  • findElement(By.tagName())
  • findElement(By.linkText())
  • findElement(By.partialLinkText())
  • findElement(By.xpath())
  • findElement(By.cssSelector())

我的想法是,将这些selector进行统一的封装,封装成一个公共的UiFinder类,在新建界面元素的查找时,只需实例化UiFinder,调用其构造函数,将selector的类型以及值传进去即可。生成的UiFinder对象就可以被UI操作的各种方法所直接调用。代码如下:

package com.javaseleniumdemo.core;

import org.openqa.selenium.By;
import org.openqa.selenium.support.How;

/**
 * @author Joy
 */
public class UiFinder {
    public By by;

    public How how;

    public String selector;

    public UiFinder(How how, String selector)
    {
        this.how = how;
        this.selector = selector;
        this.convertToBy(how, selector);
    }

    private void convertToBy(How how, String selector) {
        switch (how) {
            case ID:
                this.by = By.id(selector);
                break;
            case XPATH:
                this.by = By.xpath(selector);
                break;
            case CSS:
                this.by = By.cssSelector(selector);
                break;
            case CLASS_NAME:
                this.by = By.className(selector);
                break;
            case NAME:
                this.by = By.name(selector);
                break;
            case TAG_NAME:
                this.by = By.tagName(selector);
                break;
            case LINK_TEXT:
                this.by = By.linkText(selector);
                break;
            case PARTIAL_LINK_TEXT:
                this.by = By.partialLinkText(selector);
                break;
            default:
                break;
        }
    }
}

3.3 对UI基础操作的封装

UiActions这个类即为对UI基础操作的封装,包括查找元素、点击元素、输入字符串、页面导航、截屏等等等等。

之所以要在selenium提供的基础方法上再封装一层,不光可以应对更加复杂的页面控件以及页面操作,最重要的是,还可以将显隐式等待和异常捕获等等封装到操作方法中,这样可以极大地提高界面操作的稳定性和容错性,也能大大降低业务实现代码的冗余度。代码如下:

package com.javaseleniumdemo.core;

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;

/**
 * @author Joy
 */
public class UiActions {

    private static UiContext uiContext;

    private static WebDriver driver;

    static {
        try {
            uiContext = UiContext.getUiContext();
        } catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * description: 用于为UI操作之间留出1秒的间隔
     */
    public static void operationInterval(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }

    /**
     * description: 初始化WebDriver
     */
    public static void initialUiContext(){
        uiContext.initialUiContext();
        driver = UiContext.getDriver();
    }

    /**
     * description: 关闭WebDriver,释放资源
     */
    public static void disposeUiContext(){
        uiContext.dispose();
    }

    /**
     * description: 在界面寻找Element的基础方法,集成了隐式等待及异常处理
     * @param finder
     * @return WebElement
     */
    public static WebElement findElement(final UiFinder finder){
        WebElement result = null;
        try{
            if(finder.by != null){
                result = uiContext.getDriverWait().until(new ExpectedCondition<WebElement>() {
                    @Override
                    public WebElement apply(WebDriver driver) {
                        return driver.findElement(finder.by);
                    }
                });
            }
        } catch (UnhandledAlertException f){
            String alertText = UiActions.acceptAlert();
            System.out.println("Alert消息:" + alertText + " !");
        } catch (Exception e){
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return result;
    }

    /**
     * description: 在界面寻找一组Element的基础方法,集成了隐式等待及异常处理
     * @param finder: 注意此finder应该为可以定位到一组element的finder
     * @return List<WebElement>
     */
    public static List<WebElement> findElements(final UiFinder finder){
        List<WebElement> results = null;
        try{
            if(finder.by != null){
                results = uiContext.getDriverWait().until(new ExpectedCondition<List<WebElement>>() {
                    @Override
                    public List<WebElement> apply(WebDriver driver) {
                        return driver.findElements(finder.by);
                    }
                });
            }
        } catch (UnhandledAlertException f){
            String alertText = UiActions.acceptAlert();
            System.out.println("Alert消息:" + alertText + " !");
        } catch (Exception e){
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return results;
    }

    /**
     * description: 寻找,然后点击页面元素
     * @param finder
     */
    public static void click(UiFinder finder) throws InterruptedException {

        WebElement element = findElement(finder);

        Actions actions = new Actions(driver);
        actions.moveToElement(element);
        actions.click(element);
        actions.build().perform();
        operationInterval(1000);
    }

    /**
     * description: 寻找,然后清空,再在输入框内输入text
     * @param finder,text
     */
    public static void sendKeys(UiFinder finder, String text) throws InterruptedException {

        WebElement element = findElement(finder);
        try {// 因为SendKeys默认是追加,所以先清空
            element.clear();
        }
        catch (Exception e){
            element.sendKeys("");
        }
        element.sendKeys(text);
        operationInterval(1000);
    }

    /**
     * description: 用于在dropdown menu中寻找并点击指定的option
     * @param dropDownFinder,optionFinder
     */
    public static void selectDropDownValueByText(UiFinder dropDownFinder, UiFinder optionFinder) throws InterruptedException {
        //定位下拉框,并点击打开下拉框
        WebElement dropDownElement = findElement(dropDownFinder);
        dropDownElement.click();

        operationInterval(1000);
        WebElement optionElement = findElement(optionFinder);
        optionElement.click();
    }

    /**
     * description: 用于导航到指定的url
     * @param url
     */
    public static void navigateTo(String url) throws InterruptedException {
        driver.get(url);
        operationInterval(1000);
    }

    /**
     * description: 用于判断页面是否存在指定的Element
     * @param finder
     * @return Boolean
     */
    public static Boolean probeElement(UiFinder finder){
        WebElement result = null;
        try {
            result = findElement(finder);
        }catch (NoSuchElementException e){
            System.out.println("Element not found : " + finder.toString());
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
        return result != null;
    }

    /**
     * description: 用于在页面的frame之间跳转,注意,只能从外部跳入frame,或者从父frame跳到子frame或更下级
     * @param finder
     */
    public static void switchToFrame(UiFinder finder) throws InterruptedException {
        driver.switchTo().frame(findElement(finder));
        operationInterval(1000);
    }

    /**
     * description: 用于在页面的frame之间跳转,注意,只能从子frame跳到上面一层的父frame
     */
    public static void switchToParentFrame() throws InterruptedException {
        driver.switchTo().parentFrame();
        operationInterval(1000);
    }

    /**
     * description: 从任一frame中跳出,回到主content
     */
    public static void switchToDefaultContent() throws InterruptedException {
        driver.switchTo().defaultContent();
        operationInterval(1000);
    }

    /**
     * description: 当页面跳转产生多个窗口时,使用此方法,通过窗口的index来进行切换
     * @param i
     */
    public static void switchToWindow(Integer i){
        //获得所有窗口句柄
        Set<String> handles = driver.getWindowHandles();

        if(handles != null && handles.size() > 1){
            String handle = handles.toArray()[i].toString();
            driver.switchTo().window(handle);
        }
    }

    /**
     * description: 关闭当前窗口
     */
    public static void closeCurrentWindow(){
        driver.close();
    }

    /**
     * description: 关闭所有窗口
     */
    public static void closeAllWindows(){
        //获得所有窗口句柄
        Set<String> handles = driver.getWindowHandles();
        for (String handle: handles) {
            driver.switchTo().window(handle);
            driver.close();
        }
    }

    /**
     * description: 获取页面
     */
    public static String getPageSource(){
        return driver.getPageSource();
    }

    /**
     * description: 检查页面的元素是否为enabled的(常用于判断是否还有下一页)
     * @param finder
     */
    public static Boolean checkIfElementIsEnabled(UiFinder finder){

        Boolean result = false;
        WebElement element = findElement(finder);

        String classValue = element.getAttribute("class") == null? "" : element.getAttribute("class");
        String disabledValue = element.getAttribute("disabled") == null? "" : element.getAttribute("disabled");

        result = element.isEnabled()
                && !classValue.contains("disabled")
                && !classValue.contains("Disabled")
                && !disabledValue.contains("disabled");

        return result;

    }

    /**
     * description: 获取页面的元素的text
     * @param finder
     */
    public static String getElementText(UiFinder finder){
        return findElement(finder).getText();
    }

    /**
     * description: catch页面的alert弹窗,并accept
     */
    public static String acceptAlert(){
        String alertMessage = "";
        try {
            Alert alert = driver.switchTo().alert();
            alertMessage = alert.getText();
            alert.accept();
        } catch(NoAlertPresentException e){
            e.printStackTrace();
        }
        return alertMessage;
    }

    /**
     * description: 截屏
     */
    public static void captureScreenshot(){
        File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);

        try {
            FileUtils.copyFile(srcFile,new File("screenshot.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.编写业务代码,检查运行结果

由于篇幅不宜太长,所以我把“如何调用上述代码,检查运行结果”等内容放到下一篇博客《Java Selenium框架的开发和优化教程-02》中再做描述~~

发布了42 篇原创文章 · 获赞 15 · 访问量 9789

猜你喜欢

转载自blog.csdn.net/weixin_40326608/article/details/100931563