Seleium的BUG:页面元素文案重复空格处理

前言

  • 需求

做 WEB 的 UI 自动化练习,其需求为:

访问慕课网的实战页面,获取实战页面的课程列表信息,并逐个点击进入详情并且关闭详情,直到最后一个。

  • 环境

    • Java 8
    • Maven
    • Selenium 4.0
    • Junit 5

初步代码

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class imoocTest {
    private static WebDriver driver;

    @BeforeAll
    static void setUP() {
        // 获取当前文件下所在的目录
        String currentDirectory = System.getProperty("user.dir");
        // 设置chromeDriver
        System.setProperty("webdriver.chrome.driver", currentDirectory + "/src/test/resources/Driver/Chrome/chromedriver");
        ChromeOptions options = new ChromeOptions();
        //浏览器模拟手机模式
        options.addArguments("--disable-web-security");
        // 最大化窗口
        options.addArguments("--start-maximized");
        //解决报 403 问题
        options.addArguments("--remote-allow-origins=*");
        driver = new ChromeDriver(options);
        // 隐式调用
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @AfterAll
    static void tearDown() {
        //关闭浏览器
        driver.quit();
    }

    @Test
    public void test() throws InterruptedException {
        // 打开慕课网实战的页面
        driver.get("https://coding.imooc.com/");
        // 获取当前页面的句柄
        String mainWindowHandle = driver.getWindowHandle();
        // 获取下一页按钮的元素
        WebElement nextPage = driver.findElement(By.xpath("//a[text()='下一页']"));
        // 判断下一页按钮是否存在
        while (nextPage != null) {
            // 获取当前页面的所有课程名称
            List<WebElement> courseNameList = driver.findElements(By.cssSelector(".title.ellipsis2"));
            for (int i = 0; i < courseNameList.size(); i++) {
                // 获取当前课程并且点击进入详情页面
                String courseName=courseNameList.get(i).getText();
                System.out.println(i + ":获取到的课程名称1:" + courseName);
                driver.findElement(By.xpath("//p[text()='" + courseName + "']")).click();
                Thread.sleep(2000);
                // 获取浏览器所有的窗口句柄
                Set<String> allWindowHandles = driver.getWindowHandles();
                for (String windowHandle : allWindowHandles) {
                    if (!windowHandle.equals(mainWindowHandle)) {
                        driver.switchTo().window(windowHandle);
                        break;
                    }
                }
                // 关闭详情页面
                driver.close();
                // 将句柄切换到主页面
                driver.switchTo().window(mainWindowHandle);
                Thread.sleep(2000);
            }
            // 点击下一页
            nextPage.click();
            Thread.sleep(1000);
            // 判断下一页元素是否存在,不存在则捕获异常并将下一页元素赋值为null
            try {
                nextPage = driver.findElement(By.xpath("//a[text()='下一页']"));
            } catch (Exception e) {
                nextPage = null;
                System.out.println("没有下一页了");
            }
        }
    }
}

问题现象

代码完成之后,执行代码。前 12 个元素执行均正常,但是执行到 13 个元素(从0到1落地微前端架构, MicroApp实战招聘网站)时,报NoSuchElementException 错误,即元素信息未找到。

问题分析

看到报错信息是NoSuchElementException,元素找不到。

首先查看报错的行数,第 68 行,即:driver.findElement(By.xpath(“//p[text()='” + courseName + “']”)).click();

由于这个是第 13 个元素信息,前 12 个元素信息均正常执行,无报错。则说明代码上没有问题。

那重点就关注到courseName(课程名字)这个参数。

通过打印信息,代码中获取到 courseName 文案为: 从0到1落地微前端架构, MicroApp实战招聘网站

通过网站的开发者工具,获取到页面中的 courseName 文案为:从0到1落地微前端架构, MicroApp实战招聘网站

通过对比,发现两者的区别在于在 MicroApp 前面,代码中获取到文案只有 1 个空格,而开发者工具获取到的文案有两个空格。

故导致后续的元素定位失败。

问题解决

通过百度等搜索,发现在2022 年 6 月的时候 Selenium 的 github 上就一个这样的 issues:getText() returns inconsistent result across browsers when element text contains redundant whitespaces(Issues · SeleniumHQ/selenium · GitHub

此 issues 上遇到的问题,即和我遇到的问题一样,在元素信息中有两个或者两个以上的空格,但是通过 getText()方法获取到的文案,只有一个空格。
WX20240102-193653

看到该issues 在 2022 年 10 月显示关闭,第一想法就是更新下selenium-java的版本。于是将 pom 文件的selenium-java版本从 4.0.0-rc-2 升级到 4.15.0 版本。

PS:

selenium-java 在 4.8.0 版本开始 Java 的版本需要再 Java 11 及其以上版本

Java8 版本是无法运行selenium-java 的 4.8.0 以及以上版本的。

如执行,则会报如下错误:

升级到 4.15.0 版本之后,再次执行代码。发现错误依旧,即issues 关闭了,但是 pr 还没提成功,那没有办法只能尝试其他办法了。

在升级selenium-java版本无效之后,只有尝试万能的 JS 大法。通过 Js 获取到页面元素的文案信息。

即:

String text = (String)js.executeScript(“return arguments[0].innerHTML”, element);

String text = (String)js.executeScript(“return arguments[0].textContent”, element);

代码调整

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class imoocTest {
    private static WebDriver driver;

    @BeforeAll
    static void setUP() {
        // 获取当前文件下所在的目录
        String currentDirectory = System.getProperty("user.dir");
        // 设置chromeDriver
        System.setProperty("webdriver.chrome.driver", currentDirectory + "/src/test/resources/Driver/Chrome/chromedriver");
        ChromeOptions options = new ChromeOptions();
        //浏览器模拟手机模式
        options.addArguments("--disable-web-security");
        // 最大化窗口
        options.addArguments("--start-maximized");
        //解决报 403 问题
        options.addArguments("--remote-allow-origins=*");
        driver = new ChromeDriver(options);
        // 隐式调用
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @AfterAll
    static void tearDown() {
        //关闭浏览器
        driver.quit();
    }

    @Test
    public void test() throws InterruptedException {
        // 打开慕课网实战的页面
        driver.get("https://coding.imooc.com/");
        // 获取当前页面的句柄
        String mainWindowHandle = driver.getWindowHandle();
        // 将driver转换为JavascriptExecutor以便执行JavaScript代码
        JavascriptExecutor js = (JavascriptExecutor) driver;
        // 获取下一页按钮的元素
        WebElement nextPage = driver.findElement(By.xpath("//a[text()='下一页']"));
        // 判断下一页按钮是否存在
        while (nextPage != null) {
            // 获取当前页面的所有课程名称
            List<WebElement> courseNameList = driver.findElements(By.cssSelector(".title.ellipsis2"));
            for (int i = 0; i < courseNameList.size(); i++) {
                // 获取当前课程并且点击进入详情页面
                String courseName =(String)js.executeScript("return arguments[0].textContent", courseNameList.get(i));
                System.out.println(i + ":获取到的课程名称1:" + courseName);
                driver.findElement(By.xpath("//p[text()='" + courseName + "']")).click();
                Thread.sleep(2000);
                // 获取浏览器所有的窗口句柄
                Set<String> allWindowHandles = driver.getWindowHandles();
                for (String windowHandle : allWindowHandles) {
                    if (!windowHandle.equals(mainWindowHandle)) {
                        driver.switchTo().window(windowHandle);
                        break;
                    }
                }
                driver.close();
                driver.switchTo().window(mainWindowHandle);
                Thread.sleep(2000);
            }
            nextPage.click();
            Thread.sleep(1000);
            try {
                nextPage = driver.findElement(By.xpath("//a[text()='下一页']"));
            } catch (Exception e) {
                nextPage = null;
                System.out.println("没有下一页了");
            }
        }
    }
}

脚本执行成功,无异常报错。执行结果如下: