Selenium如何获取页面text()文本

前言

练习:

访问 Testerhome 的精华帖网址:https://testerhome.com/topics/excellent。

通过浏览器的开发者工具查看精华帖的元素信息如下:

要求:

根据a标签的中的 title 属性进行元素获取,实现获取到精华帖的帖子标题,并且逐步点击该页精华帖进入帖子详情并回退。

  • 环境

    • Java 8
    • Maven
    • Selenium 4.0
    • Junit 5

尝试

通过开发者工具可以看到a标签的 title 属性的值其实就是帖子标题,即问题可以转化为获取到精华帖的标题。

根据 xpath 定位,即"//div[@class=‘title media-heading’]/a/text()",即可定位到帖子标题

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
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.List;

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 {
        // 打开testerhome的精华帖网站
        driver.get("https://testerhome.com/topics/excellent");
        // 获取帖子标题
        List<WebElement> postTitleList = driver.findElements(By.xpath("//div[@class='title media-heading']/a/text()"));

        for (int i = 0; i < postTitleList.size(); i++) {
            String postTitle = postTitleList.get(i).getText();
            Thread.sleep(1000);
            System.out.println(i + ":" + postTitle);
            Thread.sleep(1000);
            // 点击帖子,进入帖子详情页面
            driver.findElement(By.xpath("//a[@title='" + postTitle.trim() + "']")).click();
            // 获取帖子详情页面的标题
            String post = driver.findElement(By.xpath("//h1[@class='media-heading']/span[@class='title']")).getText().trim();
            // 帖子列表的标题和帖子详情的标题进行对比
            Assertions.assertEquals(postTitle, post, "帖子标题不一致");
        }
    }
}

执行代码,报错,报错信息

org.openqa.selenium.InvalidSelectorException: invalid selector: The result of the xpath expression "//div[@class='title media-heading']/a/text()" is: [object Text]. It should be an element.
  (Session info: chrome=119.0.6045.199)
For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#invalid-selector-exception
Build info: version: '4.15.0', revision: '1d14b5521b'
System info: os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '13.6', java.version: '11.0.21'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Command: [f7d4c506c9833333edcd2dc8f0c9451d, findElements {using=xpath, value=//div[@class='title media-heading']/a/text()}]
Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 119.0.6045.199, chrome: {chromedriverVersion: 119.0.6045.105 (38c72552c5e..., userDataDir: /var/folders/9l/slrbyxhs46l...}, fedcm:accounts: true, goog:chromeOptions: {debuggerAddress: localhost:58406}, networkConnectionEnabled: false, pageLoadStrategy: normal, platformName: mac, proxy: Proxy(), se:cdp: ws://localhost:58406/devtoo..., se:cdpVersion: 119.0.6045.199, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, webauthn:extension:credBlob: true, webauthn:extension:largeBlob: true, webauthn:extension:minPinLength: true, webauthn:extension:prf: true, webauthn:virtualAuthenticators: true}
Session ID: f7d4c506c9833333edcd2dc8f0c9451d
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
	at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.createException(W3CHttpResponseCodec.java:200)
	at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:133)
	at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:52)
	at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:191)
	at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:200)
	at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:175)
	at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:607)
	at org.openqa.selenium.remote.ElementLocation$ElementFinder$2.findElements(ElementLocation.java:182)
	at org.openqa.selenium.remote.ElementLocation.findElements(ElementLocation.java:103)
	at org.openqa.selenium.remote.RemoteWebDriver.findElements(RemoteWebDriver.java:377)
	at org.openqa.selenium.remote.RemoteWebDriver.findElements(RemoteWebDriver.java:371)
	at imoocTest.test(imoocTest.java:47)

这个报错信息org.openqa.selenium.InvalidSelectorException: invalid selector: The result of the xpath expression “//div[@class=‘title media-heading’]/a/text()” is: [object Text]. It should be an element.是Selenium在尝试通过XPath表达式查找元素时抛出的异常。

当使用XPath表达式//div[@class=‘title media-heading’]/a/text()时,该表达式的结果指向了一个文本节点(Text Node),而不是一个元素节点(Element Node)。Selenium的findElement或findElements方法期望返回的是DOM中的元素。然而,在XPath语法中, /text()选择器用于获取元素内的纯文本内容,它不会返回元素本身,而是返回包含在该元素内的文本内容。
因此,当尝试用上述XPath表达式定位元素时,Selenium发现结果是一个文本节点而非元素节点,所以抛出了InvalidSelectorException,提示所选表达式的结果应该是元素类型,而实际上得到了文本类型。

解决

首先想到的方法是通过获取div下a标签内的文本内容,正确的做法是在找到a元素后,通过调用.getText()方法来获取其文本内容。获取到的文本信息如下:

自动化工具 一个基于多接口的业务自动化测试框架

在通过 String 的 split 方法,通过空格进行分割,获取到标题。

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
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.List;

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 {
        // 打开testerhome的精华帖网站
        driver.get("https://testerhome.com/topics/excellent");
        // 获取帖子标题
        List<WebElement> postTitleList = driver.findElements(By.xpath("//div[@class='title media-heading']/a"));

        for (int i = 0; i < postTitleList.size(); i++) {
            String postTitle = postTitleList.get(i).getText().split(" ")[1];
            Thread.sleep(1000);
            System.out.println(i + ":" + postTitle);
            Thread.sleep(1000);
            // 点击帖子,进入帖子详情页面
            driver.findElement(By.xpath("//a[@title='" + postTitle.trim() + "']")).click();
            // 获取帖子详情页面的标题
            String post = driver.findElement(By.xpath("//h1[@class='media-heading']/span[@class='title']")).getText().trim();
            // 帖子列表的标题和帖子详情的标题进行对比
            Assertions.assertEquals(postTitle, post, "帖子标题不一致");
        }
    }
}

再次执行,发下执行几个之后,报错。其原因是如图中第四个帖子,有些帖子中有空格,导致用空格进行分割后获取到的帖子标题缺失,导致的元素定位失败。

那如何办呢? 这种时刻就需要动用万能的 Js 大法了。

之前已经用 xpath 定位,即"//div[@class=‘title media-heading’]/a/text()",即可定位到帖子标题。

但是此时有个问题,就是我对于 Js 只是了解个大概,不知道如何将 xpth 定位转为 Js 代码。

虽然我不知道如何去转化,但是可以活用工具,例如大模型就可以帮助我们快速的转化。

使用阿里的通义千问,询问它如何进行转化,提高自己的效率。

image

image

获取到代码之后,修改自己的代码

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
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.ArrayList;
import java.util.List;

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 {
        // 打开testerhome的精华帖网站
        driver.get("https://testerhome.com/topics/excellent");

        // 创建一个JavaScriptExecutor对象
        JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;
        String script = "var elements = document.querySelectorAll('.title.media-heading a');" +
                "return Array.prototype.map.call(elements, function(e) { return e.textContent.trim(); });";
        // 使用JavaScriptExecutor执行脚本并获取结果
        Object result = jsExecutor.executeScript(script);

        // 将结果转换为List<String>
        List<String> postTitleList = new ArrayList<>();
        if (result instanceof List<?>) {
            for (Object text : (List<Object>) result) {
                if (text != null) {
                    postTitleList.add((String) text);
                }
            }
        }

        for (int i = 0; i < postTitleList.size(); i++) {
            String postTitle = postTitleList.get(i).split("\\n")[1].trim();
            Thread.sleep(1000);
            System.out.println(i + ":" + postTitle);
            Thread.sleep(1000);
            driver.findElement(By.xpath("//a[@title='" + postTitle.trim() + "']")).click();
            String post = driver.findElement(By.xpath("//h1[@class='media-heading']/span[@class='title']")).getText().trim();
            if (postTitle.equals(post)) {
                System.out.println(i + ":相同_" + post);
                driver.navigate().back();
                Thread.sleep(1000);
            } else {
                System.out.println(i + ":不相同_" + post);
            }
        }
    }
}

再次执行,在执行到第 25 个的时候报错。提示java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1。第 67 行报错。在根据开发根据查看,发现在根据 xpath 定位,将急聘模块也获取到了,导致报错。

更新 xpath 定位,修改为://div[@class=‘col-lg-9’]//div[@class=‘title media-heading’]/a/text(),在利用阿里的通义千问转化为 Js 脚本。代码如下:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
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.ArrayList;
import java.util.List;

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 {
        // 打开testerhome的精华帖网站
        driver.get("https://testerhome.com/topics/excellent");

        // 创建一个JavaScriptExecutor对象
        JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;
        String script = "var elements = document.querySelectorAll('div.col-lg-9 div.title.media-heading a');" +
                "return Array.prototype.map.call(elements, function(e) { return e.textContent.trim(); });";
        // 使用JavaScriptExecutor执行脚本并获取结果
        Object result = jsExecutor.executeScript(script);

        // 将结果转换为List<String>
        List<String> postTitleList = new ArrayList<>();
        if (result instanceof List<?>) {
            for (Object text : (List<Object>) result) {
                if (text != null) {
                    postTitleList.add((String) text);
                }
            }
        }

        for (int i = 0; i < postTitleList.size(); i++) {
            String postTitle = postTitleList.get(i).split("\\n")[1].trim();
            Thread.sleep(1000);
            System.out.println(i + ":" + postTitle);
            Thread.sleep(1000);
            driver.findElement(By.xpath("//a[@title='" + postTitle.trim() + "']")).click();
            String post = driver.findElement(By.xpath("//h1[@class='media-heading']/span[@class='title']")).getText().trim();
            if (postTitle.equals(post)) {
                System.out.println(i + ":相同_" + post);
                driver.navigate().back();
                Thread.sleep(1000);
            } else {
                System.out.println(i + ":不相同_" + post);
            }
        }
    }
}

执行代码,顺利执行完成,无报错信息,亦也完成了要求。