You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Java Selenium选择下拉选项后提交按钮未启用问题

问题描述

我用Java Selenium做前端自动化测试,页面有两个下拉菜单(whiteplayername和blackplayername)和一个提交按钮(submit-button),规则是两个下拉都选完选项后,提交按钮才会启用。手动操作页面功能正常,但跑Selenium测试时,明明选了两个下拉选项,提交按钮却一直没启用。我怀疑是选择操作没触发页面事件,初始测试代码如下:

@Test
@DisplayName("When whitePlayerName and blackPlayerName is selected, submit button is active")
void tesSubmitPage_whenNameIsSelectedFromBothDropDownMenus_submitButtonIsEnabled() {
    List<WebElement> elements = driver.findElements(By.className("material-icons"));
    elements.get(1).click();
    
    WebElement dropdownWhite = driver.findElement(By.id("whiteplayername"));
    Select selectWhite = new Select(dropdownWhite);
    selectWhite.selectByValue("Hywel Griffiths");
    WebElement dropdownBlack = driver.findElement(By.id("blackplayername"));
    Select selectBlack = new Select(dropdownBlack);
    selectBlack.selectByValue("Hywel Griffiths");
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    wait.until(ExpectedConditions.elementToBeClickable(By.id("submit-button")));
    WebElement button = driver.findElement(By.id("submit-button"));

    assertTrue("", button.isEnabled());
}

页面关联的JavaScript代码:

<script type="module">
    document.getElementById("submit-button").disabled = true;
    const form = document.getElementById("form");
    const submitButton = document.getElementById("submit-button");
    const whitePlayerName = document.getElementById("whiteplayername");
    const blackPlayerName = document.getElementById("blackplayername");
    const firstNameBlack = document.getElementById("firstnameblack");
    const firstNameWhite = document.getElementById("firstnamewhite");
    const lastNameWhite = document.getElementById("lastnamewhite");
    const lastNameBlack = document.getElementById("lastnameblack");

    form.addEventListener('change', () => {
        const whiteNameSelected = whitePlayerName.value;

        // Disable white name inputs if white name is selected in the dropdown
        firstNameWhite.disabled = whiteNameSelected;
        lastNameWhite.disabled = whiteNameSelected;

        // Determine if the white name is either selected or typed in the inputs
        const validWhiteName = whiteNameSelected || (firstNameWhite.value && lastNameWhite.value);

        const blackNameSelected = blackPlayerName.value;

        // Disable black name inputs if black name is selected in the dropdown
        firstNameBlack.disabled = blackNameSelected;
        lastNameBlack.disabled = blackNameSelected;

        // Determine if the black name is either selected or typed in the inputs
        const validBlackName = blackNameSelected || (firstNameBlack.value && lastNameBlack.value);
        
        const submitAvailable = validWhiteName && validBlackName && validSpaces;
        submitButton.disabled = !submitAvailable;

        if (!validWhiteName) {
            document.getElementById("warningtext").textContent = "Please select at least one  white name.";
        } else if (!validBlackName) {
            document.getElementById("warningtext").textContent = "Please select at least one  black name.";
        } else if (!validSpaces) {
            document.getElementById("warningtext").textContent = "Please do not included spaces in the names.";
        } else {
            document.getElementById("warningtext").textContent = "";
        }
    });

    form.addEventListener('input', () => {
        const whiteNameSelected = whitePlayerName.value;

        // Disable white name inputs if white name is selected in the dropdown
        firstNameWhite.disabled = whiteNameSelected;
        lastNameWhite.disabled = whiteNameSelected;

        // Determine if the white name is either selected or typed in the inputs
        const validWhiteName = whiteNameSelected || (firstNameWhite.value && lastNameWhite.value);

        const blackNameSelected = blackPlayerName.value;

        // Disable black name inputs if black name is selected in the dropdown
        firstNameBlack.disabled = blackNameSelected;
        lastNameBlack.disabled = blackNameSelected;

        // Determine if the black name is either selected or typed in the inputs
        const validBlackName = blackNameSelected || (firstNameBlack.value && lastNameBlack.value);

        const validSpaces = !lastNameWhite.value.match(/\s+/g) && !firstNameWhite.value.match(/\s+/g) && !lastNameBlack.value.match(/\s+/g) && !firstNameBlack.value.match(/\s+/g);

        const submitAvailable = validWhiteName && validBlackName && validSpaces;
        submitButton.disabled = !submitAvailable;

        if (!validWhiteName) {
            document.getElementById("warningtext").textContent = "Please select at least one  white name.";
        } else if (!validBlackName) {
            document.getElementById("warningtext").textContent = "Please select at least one  black name.";
        } else if (!validSpaces) {
            document.getElementById("warningtext").textContent = "Please do not included spaces in the names.";
        } else {
            document.getElementById("warningtext").textContent = "";
        }
    });
</script>

对应的Thymeleaf HTML代码:

<body style="background-color:rgb(68, 57, 57);">
<div class="warning">
<p id="warningtext"></p><br>
</div>
<div class="topnav">
<a th:href="@{main.html}"><i class="material-icons"
                             style="border:2px;font-size:60px;color:rgb(0, 0, 
0);">arrow_back</i></a>
</div>
<div class="formformat">
<form th:object="${game}" th:action="@{/addgame}" th:method="post" id="form">
    <label for="whiteplayername">Select white player:</label>
    <select name="whiteplayername" id="whiteplayername" th:object="${names}" th:field="${game.whitePlayerName}">
        <option th:value="null" th:selected="${game.name == null}"></option>
        <th:block th:each="name : ${names}">
            <option th:value="${name.name}"
                    th:text="${name.name}"></option>
        </th:block>
    </select>
    <label for="blackplayername">Select black player:</label>
    <select name="blackplayername" id="blackplayername" th:object="${names}" 
th:field="${game.blackPlayerName}">
        <option th:value="null" th:selected="${game.name == null}"></option>
        <th:block th:each="name : ${names}">
            <option th:value="${name.name}"
                    th:text="${name.name}"></option>
        </th:block>
    </select><br><br>
    <label for="firstnamewhite">First name white:</label>
    <input type="text" id="firstnamewhite" th:field="*{firstNameWhite}"/>
    <label for="firstnameblack">First name black:</label>
    <input type="text" id="firstnameblack" th:field="*{firstNameBlack}"/><br><br>
    <label for="lastnamewhite">Last name white:</label>
    <input type="text" id="lastnamewhite" th:field="*{lastNameWhite}"/>
    <label for="lastnameblack">Last name black:</label>
    <input type="text" id="lastnameblack" th:field="*{lastNameBlack}"/><br><br>
    <label for="date">Date:</label><br>
    <input type="date" id="date" th:field="*{date}">
    <table>
        <tr>
            <th>Move</th>
            <th>White</th>
            <th>Black</th>
        </tr>
        <tr>
            <td>1</td>
            <td><input type="text" id="white1" th:field="*{moves}"></td>
            <td><input type="text" id="black1" th:field="*{moves}"></td>
        </tr>
    </table>
    <input type="submit" value="Submit" id="submit-button">
</form>

我试过修改测试代码,用JavascriptExecutor触发表单的change事件,但问题还是没解决,修改后的测试代码如下:

@Test
@DisplayName("When whitePlayerName and blackPlayerName is selected, submit button is active")
void tesSubmitPage_whenNameIsSelectedFromBothDropDownMenus_submitButtonIsEnabled() throws InterruptedException {
    List<WebElement> elements = driver.findElements(By.className("material-icons"));
    elements.get(1).click();

    JavascriptExecutor js = (JavascriptExecutor) driver;

    WebElement whitePlayerNameDropdown = driver.findElement(By.id("whiteplayername"));
    WebElement form = driver.findElement(By.id("form"));
    Select whitePlayerNameSelect = new Select(whitePlayerNameDropdown);
    whitePlayerNameSelect.selectByValue("Hywel Griffiths");
    js.executeScript("arguments[0].dispatchEvent(new Event('change'))", form);

    WebElement blackPlayerNameDropdown = driver.findElement(By.id("blackplayername"));
    Select blackPlayerNameSelect = new Select(blackPlayerNameDropdown);
    blackPlayerNameSelect.selectByValue("Hywel Griffiths");
    js.executeScript("arguments[0].dispatchEvent(new Event('change'))", form);

    WebElement button = driver.findElement(By.id("submit-button"));
    assertTrue("", button.isEnabled());
}

需要找出问题并解决。


问题原因及解决办法

1. 核心问题:validSpaces变量未定义导致逻辑中断

看你提供的JavaScript代码,form的change事件监听器里用到了validSpaces变量,但这个变量只在input事件监听器里定义过,change事件里完全没声明它。手动操作时,可能因为触发过输入框的input事件,变量被初始化;但Selenium只操作下拉菜单,不会触发输入框的input事件,导致validSpaces为undefined,validWhiteName && validBlackName && validSpaces结果为false,提交按钮因此一直处于禁用状态。

2. 解决步骤

步骤一:修复JavaScript代码的变量定义问题

把验证逻辑抽成独立函数,让两个事件监听器都调用这个函数,既避免重复代码,也解决变量未定义问题:

<script type="module">
    document.getElementById("submit-button").disabled = true;
    const form = document.getElementById("form");
    const submitButton = document.getElementById("submit-button");
    const whitePlayerName = document.getElementById("whiteplayername");
    const blackPlayerName = document.getElementById("blackplayername");
    const firstNameBlack = document.getElementById("firstnameblack");
    const firstNameWhite = document.getElementById("firstnamewhite");
    const lastNameWhite = document.getElementById("lastnamewhite");
    const lastNameBlack = document.getElementById("lastnameblack");

    // 抽离验证逻辑为独立函数
    function validateForm() {
        const whiteNameSelected = whitePlayerName.value;
        firstNameWhite.disabled = whiteNameSelected;
        lastNameWhite.disabled = whiteNameSelected;
        const validWhiteName = whiteNameSelected || (firstNameWhite.value && lastNameWhite.value);

        const blackNameSelected = blackPlayerName.value;
        firstNameBlack.disabled = blackNameSelected;
        lastNameBlack.disabled = blackNameSelected;
        const validBlackName = blackNameSelected || (firstNameBlack.value && lastNameBlack.value);

        const validSpaces = !lastNameWhite.value.match(/\s+/g) && !firstNameWhite.value.match(/\s+/g) && !lastNameBlack.value.match(/\s+/g) && !firstNameBlack.value.match(/\s+/g);
        
        const submitAvailable = validWhiteName && validBlackName && validSpaces;
        submitButton.disabled = !submitAvailable;

        if (!validWhiteName) {
            document.getElementById("warningtext").textContent = "Please select at least one white name.";
        } else if (!validBlackName) {
            document.getElementById("warningtext").textContent = "Please select at least one black name.";
        } else if (!validSpaces) {
            document.getElementById("warningtext").textContent = "Please do not include spaces in the names.";
        } else {
            document.getElementById("warningtext").textContent = "";
        }
    }

    // 两个事件都调用同一验证函数
    form.addEventListener('change', validateForm);
    form.addEventListener('input', validateForm);
</script>

步骤二:优化Selenium测试代码

  • 直接触发下拉元素的change事件,更贴近用户实际操作;
  • 保留显式等待,确保按钮状态更新后再断言:
@Test
@DisplayName("When whitePlayerName and blackPlayerName is selected, submit button is active")
void tesSubmitPage_whenNameIsSelectedFromBothDropDownMenus_submitButtonIsEnabled() {
    List<WebElement> elements = driver.findElements(By.className("material-icons"));
    elements.get(1).click();

    JavascriptExecutor js = (JavascriptExecutor) driver;

    // 处理白方下拉
    WebElement whiteDropdown = driver.findElement(By.id("whiteplayername"));
    Select whiteSelect = new Select(whiteDropdown);
    whiteSelect.selectByValue("Hywel Griffiths");
    // 触发下拉元素的change事件(带冒泡,确保父元素能捕获)
    js.executeScript("arguments[0].dispatchEvent(new Event('change', {bubbles: true}))", whiteDropdown);

    // 处理黑方下拉
    WebElement blackDropdown = driver.findElement(By.id("blackplayername"));
    Select blackSelect = new Select(blackDropdown);
    blackSelect.selectByValue("Hywel Griffiths");
    js.executeScript("arguments[0].dispatchEvent(new Event('change', {bubbles: true}))", blackDropdown);

    // 显式等待按钮启用
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    WebElement submitButton = wait.until(driver -> {
        WebElement btn = driver.findElement(By.id("submit-button"));
        return btn.isEnabled() ? btn : null;
    });

    assertTrue(submitButton.isEnabled());
}

3. 额外注意点

  • HTML中Thymeleaf的th:selected="${game.name == null}"可能有误,应该对应game.whitePlayerName == null和game.blackPlayerName == null,不过这和当前问题无关,但可能影响初始状态;
  • 手动操作时触发过输入框input事件,掩盖了change事件里的变量未定义问题,Selenium的操作刚好暴露了这个代码漏洞。

内容的提问来源于stack exchange,提问作者Hywel Griffiths

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 16:51:37