Ohhnews

分类导航

$ cd ..
Baeldung原文

Selenium WebDriver submit() 与 click() 对比详解

#selenium webdriver#自动化测试#表单提交#click()#submit()

1. 引言

很难想象进行 Web 应用自动化时无需点击任何元素或提交表单。尽管这些操作看似简单,但其中存在一些值得注意的细微差别。

在本教程中,我们将探讨 Selenium 如何处理 click()submit(),以及在使用表单时我们应选择哪一种。

2. 前提条件与设置

首先,让我们搭建一个简单的 Selenium 测试。

为此,我们假设读者已基本熟悉以下几个概念

  • Java 开发环境
  • 初始化 Maven 项目
  • 运行测试

作为示例代码,我们使用一个 Maven 快速启动项目,并向 pom.xml 文件中添加 Selenium WebDriver、WebDriver Manager 和 JUnit 包:

$ xml
<dependencies>
  <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.46.0</version>
  </dependency>
  <dependency>
      <groupId>io.github.bonigarcia</groupId>
      <artifactId>webdrivermanager</artifactId>
      <version>6.3.0</version>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11</version>
    <scope>test</scope>
  </dependency>
</dependencies>

让我们利用 WebDriver Manager 自动下载并设置测试中使用的浏览器驱动程序的正确版本。此外,我们使用 JUnit 对示例代码进行注解和组织。

接下来,我们在项目的 /test/java 目录中创建一个示例测试文件。命名为 ContactTest.java,并添加设置代码以开始:

$ java
public class ContactTest {
    private WebDriver driver;
    @BeforeEach
    void setup() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
    }
    @AfterEach
    void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }
    // add tests here
}

在 setup 和 teardown 函数中,我们在每个测试之前初始化 Chrome 驱动,并在每个测试之后退出驱动。

3. Selenium 的工作原理

Selenium WebDriver 通过将命令转发到相应的浏览器驱动程序(例如 Chrome 的 chromedriver 或 Firefox 的 geckodriver)来执行命令。本质上,它就像一个发送 HTTP 请求的信使。

另一方面,浏览器会保护敏感操作(也称为激活行为),例如 click()submit() 。其目的是防止恶意脚本多次触发点击以利用用户操作。这种安全措施通过 isTrusted 标志实现。所有事件都带有此标志,用于区分人类交互和脚本交互。由 JavaScript 直接调度的事件(例如通过 dispatchEvent() 调度的事件)的 isTrusted 设置为 false,而 WebDriver 用户交互命令(如 click())生成的是受信任的浏览器事件。

由于 Selenium 使用 WebDriver 协议,因此具有更高的权限,包括可以多次触发点击而不会被阻止。本质上,Selenium 触发的事件在被浏览器系统处理时,其 isTrusted 标志会被设置为 true

4. 探索 click()

就像手动操作一样,我们可以在任何可点击元素上调用 click()

4.1. 实现细节

它的设计旨在模仿人类行为。因此,元素需要处于启用、可见状态,并且具有非零尺寸才能成功点击

当 Selenium 发送点击消息时,浏览器驱动程序会执行一系列操作

  1. 确保元素可点击
  2. 滚动以使元素可见
  3. 计算元素的中心坐标以调度事件
  4. 调度 mouse downmouse up 事件
  5. 调度一个 click 事件

让我们看一下 Selenium WebDriver 代码中 click() 的实现:

$ java
@Override
public void click() {
  execute(DriverCommand.CLICK_ELEMENT(id));
}

根据文档,click() 可能会抛出以下异常:

  • NoSuchElementException:当元素不存在时
  • ElementNotInteractableException:当元素隐藏、禁用或尺寸为零时
  • ElementClickInterceptedException:当另一个元素遮挡了目标元素时
  • StaleElementReferenceException:当引用不再有效时

为了获得更可控的点击,我们可以使用 Selenium 的 Actions API:

$ java
new Actions(driver).moveToElement(submitButton).click().perform();

不过,与普通的 click() 不同,当另一个元素遮挡目标元素时,它不会抛出异常。值得注意的是,Actions 始终在指定坐标处执行点击,无论点击最终落在哪个元素上

4.2. 示例

为了演示 click() 的实际效果,我们实现并运行一个简单的测试,该测试填写表单,然后点击提交按钮

$ java
@Test
void submitFormWithClick() throws InterruptedException {
    try {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        driver.get("https://www.baeldung.com/contact");
        WebElement nameField = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.name("your-name"))
        );
        nameField.sendKeys("Jane Doe");
        TimeUnit.SECONDS.sleep(1);
        WebElement emailField = driver.findElement(By.name("your-email"));
        emailField.sendKeys("janedoe@mail.com");
        TimeUnit.SECONDS.sleep(1);
        WebElement messageField = driver.findElement(By.name("your-message"));
        messageField.sendKeys("Hello, this is an automated test message.");
        TimeUnit.SECONDS.sleep(1);
        WebElement submitButton = wait.until(
          ExpectedConditions.elementToBeClickable(By.cssSelector("input[type='submit']"))
        ); 
        submitButton.click();
        TimeUnit.SECONDS.sleep(2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在此示例中,我们使用 TimeUnit.SECONDS.sleep(1) 来跟随测试执行的进度。填写表单后,页面会向下滚动以显示提交按钮,然后点击它:

[LOADING...]

遗憾的是,该提交按钮没有点击动画来演示 UI 效果。

5. 探索 submit()

正如所料,submit()click() 更受限,因为它唯一的目的就是提交表单。

5.1. 实现细节

我们可以对表单元素调用 submit() 来触发表单提交功能。此外,我们还可以对表单内的任何元素调用 submit() 。在后一种情况下,父表单元素会被自动找到,然后 Selenium 对该表单调用 submit。

当没有表单时,该函数应抛出 NoSuchElementException。实际上,它通常抛出的是不同的异常——UnsupportedOperationException。当我们查看 submit() 的实现时,原因就很明显了:

$ java
@Override
public void submit() {
  try {
    execute(DriverCommand.SUBMIT_ELEMENT(id));
  } catch (JavascriptException ex) {
    String message = "To submit an element, it must be nested inside a form element";
    throw new UnsupportedOperationException(message);
  }
}

这段代码看起来与 click() 的代码相似。但是,调用 submit() 时不会有任何视觉反馈,因为我们是直接对表单执行提交。不会触发提交按钮或按钮点击事件。因此,任何依赖这些事件处理程序的附加功能都会被完全绕过。

此外,我们调用 submit() 的提交按钮或其他表单元素不需要是可见或可点击的

5.2. 示例

让我们添加另一个测试,复制之前的测试,重命名它,将 click() 改为 submit(),并运行结果:

$ java
@Test
void submitFormWithSubmit() throws InterruptedException {
    try {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        driver.get("https://www.baeldung.com/contact");
        WebElement nameField = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.name("your-name"))
        );
        nameField.sendKeys("Jane Doe");
        TimeUnit.SECONDS.sleep(1);
        WebElement emailField = driver.findElement(By.name("your-email"));
        emailField.sendKeys("janedoe@mail.com");
        TimeUnit.SECONDS.sleep(1);
        WebElement messageField = driver.findElement(By.name("your-message"));
        messageField.sendKeys("Hello, this is an automated test message.");
        TimeUnit.SECONDS.sleep(1);
        WebElement submitButton = wait.until(
            ExpectedConditions.elementToBeClickable(By.cssSelector("input[type='submit']"))
        ); 
        submitButton.submit();
        TimeUnit.SECONDS.sleep(2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

正如预期,页面没有滚动以显示提交按钮:

[LOADING...]

本质上,submit() 的行为完全不模仿正常的用户交互。最终结果与我们在提交按钮上调用 click() 相同。然而,这种交互纯粹是程序化的。

6. submit() 的弃用

在现代 Selenium(版本 4 及更高版本)中,不推荐使用 submit(),尽管它尚未过时,代码也没有将其标记为已弃用。实际上,官方 Selenium 文档建议不要使用 submit()。原因是浏览器驱动程序不支持。

早期版本的 Selenium 为此提供了一个专用的端点。然而,从 Selenium 3 开始,浏览器供应商负责驱动程序的实现。一些供应商立即开始严格遵守新的官方 W3C WebDriver 协议,该协议没有 submit() 规范。这打破了向后兼容性,迫使 Selenium 实现一种变通方法。

目前,submit() 不再使用旧的提交消息,而是向不同的端点发送消息以执行自定义脚本。由于浏览器驱动程序不处理此代码,因此事件的 isTrusted 标志被设置为 false

7. click()submit()

显然,由于不再推荐使用 submit(),我们应该使用 click() 来提交表单。不过,让我们暂时忽略这一点,分析一下为什么 click() 是更好的选择。

Selenium 哲学以及 UI 自动化的核心目标是尽可能模拟真实用户的操作。如果真实用户无法与元素交互以提交表单,那么一个通过编程方式实现提交的通过测试反而掩盖了潜在的可访问性问题。另一方面,click() 完美地实现了核心目标。

此外,现代 Web 应用程序并不总是遵循传统的表单结构,也不一定包含根 <form> 元素。与 submit() 不同,click() 更加灵活,适用于任何 UI 结构。

最后,安全优势也很重要。因为 click() 操作的 isTrusted 标志设置为 true,所以它是可靠的,能够正确传播一系列关联事件,并且不会被安全工具标记

8. 结论

在本文中,我们探讨了 Selenium 在底层是如何工作的。同时,我们还了解了 click()submit() 的工作原理,以及为什么应该使用 click()

虽然在实际场景中我们几乎不会想要在现代 Selenium 中使用 submit(),但 submit() 在自动化实现中凸显了重要的细微差别,具有教育意义

文章 Selenium Webdriver submit() vs click() 首次发布于 Baeldung