干货|Selenium,从入门实战

Selenium 自动化测试 模拟填单

Posted by Claire on February 16, 2023

工作中的需求是多种多样,是否有遇到过这样的需求:一个H5表单,用户通过多渠道入单,期望帮用户的下单请求提交至这个H5上。

这种通常就是在二级服务商、代理服务商的服务商会遇到这样的需求,源头供应商没有接口的能力,提供到一个页面,支持提交表单数据。

这种场景,我们可以怎样提交呢?

  • 纯人工肯定没毛病,收集到用户信息后,替用户向上游提交下单请求。可是量大就不现实了。
  • 最终肯定还是期望自动化,既保障用户隐私又提升效率,这也是智能化的趋势。

那我们可以想到的就是一些辅助自动化的工具,模拟人的动作,又不需要人来干预,其中有Selenium、Appnium。这些原理都类似,利用自动化测试的思想,来模拟进行表单提交。

以上案例是基于H5网页的,那么使用Selenium是再合适不过了。

请注意,以上Selenium是在多方认同恰当的业务流程中,进行模拟填单提交的自动化操作,不可用于不合规的牟利,目前非加白合规的流程也都会有反爬虫的措施来抵御使用Selenium自动化的一些操作。

一、从0入门Selenium

python有丰富的类库,与Selenium结合紧密,由于本人相对熟悉JAVA,以下样例主要通过java进行实现。

Selenium官方GITHUB:https://github.com/SeleniumHQ/seleniumhq.github.io

以下先介绍一些基本操作:

1.1 打开URL

ChromeOptions、ChromeDriver的详细参数可参看官网:

ChromeOptions option = new ChromeOptions();
WebDriver driver = new ChromeDriver();
driver.get("https://www.selenium.dev/selenium/web/web-form.html");

1.2 获取页面标题

String title = driver.getTitle();

1.3 配置超时等待策略

有时候页面加载需要的时间不确定,为了确保接下来获取页面元素的步骤没有问题,通常会配置一个等待时间,让页面元素有足够的时间渲染,避免脚本的异常

 driver.manage().timeouts().implicitlyWait(Duration.ofMillis(500));

1.4 查找页面元素

在使用API的时候会发现,有几种可以获取页面元素的方法,具体参照实际情况

源码中By的方法有:

    public static By id(String id) {
        return new By.ById(id);
    }

    public static By linkText(String linkText) {
        return new By.ByLinkText(linkText);
    }

    public static By partialLinkText(String partialLinkText) {
        return new By.ByPartialLinkText(partialLinkText);
    }

    public static By name(String name) {
        return new By.ByName(name);
    }

    public static By tagName(String tagName) {
        return new By.ByTagName(tagName);
    }

    public static By xpath(String xpathExpression) {
        return new By.ByXPath(xpathExpression);
    }

    public static By className(String className) {
        return new By.ByClassName(className);
    }

    public static By cssSelector(String cssSelector) {
        return new By.ByCssSelector(cssSelector);
    }

1.4.1 根据ClassName获取元素

一个页面,在特殊或者关键的DIV上,规范非低代码的平台上都会有特殊class标识,可能要定义操作或者样式

<div class="code-toolbar">
</div>

可以通过以下方法获取上面这个div

WebElement vegetable = driver.findElement(By.className("code-toolbar"));

1.4.2 根据ID获取获取页面元素

和ClassName类似,当一个页面元素没有一个单独定位的class属性,那么可以看一下是否有ID属性

<ul class="nav nav-tabs" id="tabs-1" role="tablist"></ul>

以上这个class经观察,有但不是唯一标识,我想唯一获取这个元素,那么看到ID是一个唯一的属性,那么就可以通过ID获取

WebElement fruits = driver.findElement(By.id("tabs-1"));

1.4.3 根据name获取页面元素

name和ID、ClassName,如果具有特殊性可以唯一标识,那也很好用的

WebElement fruits = driver.findElement(By.name("tabsname"));

1.4.4 根据TagName获取页面元素

有一种需要获取下拉框内的全部元素内容,或者页面的一个input框元素,可以使用TagName

List<WebElement> elements = driver.findElements(By.tagName("li"));

for (WebElement element : elements) {
    System.out.println("text:" + element.getText());
}

1.4.5 根据CSS标识获取元素

有时候页面元素存在嵌套,不希望通过ID或者ClassName层层获取,为了更加高效,可是使用CSS选择器或XPath选择器,来定位页面元素 css选择器中针对ID和ClassName存在简写

  • #表示id属性定位器,后面紧跟id属性值
  • 英文的. 表示class定位器,后面紧跟class属性值
  • 子代选择器用>表示,这个是直接隶属的关系
  • 毗邻元素选择器用+表示

这边CSS的组合写法和XPath的写法相对复杂一些,后面单独列一期

1.4.6 获取某元素的子元素

WebElement element = driver.findElement(By.tagName("div"));
// 获取这个div下面的全部的p标签内容
List<WebElement> elements = element.findElements(By.tagName("p"));
for (WebElement e : elements) {
    System.out.println(e.getText());
}

1.5 操作页面元素

1.5.1 输入框设置内容

比如我们定位了百度输入框,需要在输入框中写入内容。表单且是text类型输入框的可以填入,如果是不可编辑的则无法定位修改该元素。

driver.findElement(By.name("q")).sendKeys("q" + Keys.ENTER);
写了一个q并且键入一个ENTER

1.5.2 输入清空

一个可清空、可编辑的表单文本输入框,可以选中后进行清空。

WebElement searchInput = driver.findElement(By.name("q"));
searchInput.sendKeys("selenium");
searchInput.clear();

1.5.3 表单提交

Selenium4开始将不再实现

1.5.4 元素点击

通常提交表单都有标识性的按钮实现,所以也可以定位点击提交按钮来实现表单的提交

WebElement agreeCheck = driver.findElement(By.id("agree"));
agreeCheck.click();

1.5.5 下拉选择

需要是Select选择框可以进行以下操作,有些下拉框是原生的ul\li的,不可使用

<select name="selectomatic">
    <option selected="selected" id="non_multi_option" value="one">One</option>
    <option value="two">Two</option>
    <option value="four">Four</option>
    <option value="still learning how to count, apparently">Still learning how to count, apparently</option>
</select>
WebElement selectElement = driver.findElement(By.name("selectomatic"));
Select select = new Select(selectElement);
List<WebElement> optionList = select.getOptions();//单选
List<WebElement> selectedOptionList = select.getAllSelectedOptions();//多选

这种情况,我们通常想操控这个下拉框选择某一个元素,可以通过以下方式选择,如果某一个元素被禁用了,就不可被选中。

//根据文本选择
select.selectByVisibleText("Four");
//根据值选择
select.selectByValue("two");
//根据排序选择
select.selectByIndex(3);

1.5.6 屏幕滚动

有时候点开的页面需要通过滚动的方式,让页面元素能够展示并且获取。

//定位一个页面元素,让它滚动到视线内
WebElement element = driver.findElement(By.className("btnPay"));
driver.executeScript("arguments[0].scrollIntoView();", element);

//指定滚动一定像素
String js="var q=document.documentElement.scrollTop=10000";
driver.executeScript(js);

此外还有很多的操作场景,比如自动化文件上传下载、模拟鼠标操作、模拟键盘输入、滚轮滚动、页面截图等等

具体可以参看官方文档: https://www.selenium.dev/documentation/webdriver/getting_started/

官方也是极力推荐大家往Selenium4上面迁移,会带来一些改变,但会有更多的操作特性

二、使用案例

以下通过一个实例,来介绍使用Selenium的一些细节。

2.1 明确本地版本

需要保证浏览器(具体是Chrome/Chromium, Firefox, Internet Explorer, Edge, and Safari. 它是都能支持的,具体看自己情况)

比如我本地是Chrome,那我首先确认我的chrome版本

打开浏览器,输入:chrome://version,就能看到了

Google Chrome	110.0.5481.177 (正式版本) (x86_64) 
修订版本	f34f7ab2d4ca4ad498ef42aeba4f4eb2c1392d63-refs/branch-heads/5481@{#1239}

2.2 下载指定驱动

如果本地能够找到chrome驱动的可以直接使用,此外还可以根据目前自己的版本去官方下载:https://chromedriver.chromium.org/downloads

比如我已经明确自己是110版本,那么根据指引:If you are using Chrome version 110, please download ChromeDriver 110.0.5481.77

2.3 加载驱动的几种方法

2.3.1 Selenium v4.6 使用Selenium Manager

2.3.2 Driver Management Software

WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();

2.3.3 通过本地环境配置chromedriver

echo 'export PATH=$PATH:/path/to/driver' >> ~/.bash_profile
source ~/.bash_profile

2.3.4 通过写死驱动的路径

System.setProperty("webdriver.chrome.driver","/path/to/chromedriver");
ChromeDriver driver = new ChromeDriver();

2.4 Java 引入依赖

具体取决于各个环节的版本

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.8.0</version>
        </dependency>

2.5 撰写代码

   public static void main(String[] args) {
        System.setProperty("webdriver.chrome.driver",
                "/Users/chenzy/Downloads/chromedriver");

        ChromeOptions option = new ChromeOptions();
        option.addArguments("--start-maximized");

        ChromeDriver driver = new ChromeDriver(option);
        driver.manage().timeouts().pageLoadTimeout(3, TimeUnit.SECONDS);
        driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        try {
            driver.get("https://www.baidu.com/");
            WebElement payCode = driver.findElement(By.id("lg"));
            int x = payCode.getLocation().x;
            int y = payCode.getLocation().y;
            // Get entire page screenshot
            File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            BufferedImage fullImg = ImageIO.read(screenshot);

            // Get the location of element on the page
            Point point = payCode.getLocation();

            // Get width and height of the element
            int eleWidth = payCode.getSize().getWidth();
            int eleHeight = payCode.getSize().getHeight();

            // Crop the entire page screenshot to get only element screenshot
            BufferedImage eleScreenshot = fullImg.getSubimage(point.getX(), point.getY(),
                    eleWidth, eleHeight);
            ImageIO.write(eleScreenshot, "png", screenshot);

            // Copy the element screenshot to disk
            File screenshotLocation = new File("~/Downloads/GoogleLogo_screenshot.png");
            FileUtils.copyFile(screenshot, screenshotLocation);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }