Jack Miller
Jack Miller

Reputation: 7667

Selenium DeselectAll not working for HTML SELECT with multiple attribute

I am using this HTML code:

<select name="cars" multiple>
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="opel">Opel</option>
  <option value="audi">Audi</option>
</select>

Manually I select some items. Then I want to deselect all of them (I am using C# but that does not matter):

var carsElement = BrowserDriver.FindElementByName("cars");
var carsSelect = new SelectElement(carsElement);
carsSelect.DeselectAll();

What happens: The first selected options stays selected, others are unselected.

Looking at the code this is what must happen, because DeselectAll() calls Click() for all selected options. You can try that in your browser. This will never unselect all options (unless you hold CTRL while clicking but that is not done by the Selenium code). So the correct way would be to change DeselectAll to press CTRL while clicking as demonstrated by How to perform Control key down in selenium webdriver?

Bottom line, I know how to fix this; my questions are: Am I missing anything? Is there an easier way? Is SelectElement not intended from HTML SELECT multiple?

Upvotes: 1

Views: 2414

Answers (4)

pguardiario
pguardiario

Reputation: 55002

You can for sure deselect these with

browser.execute_script("[...document.querySelectorAll('[name=cars] option')].map(o => o.selected = false)")

Upvotes: 2

undetected Selenium
undetected Selenium

Reputation: 193218

I have verified your usecase of using the deselect_all() method through the Selenium Python Client and it seems to work perfecto.

deselect_all()

deselect_all() method clears all selected entries. This is only valid when the SELECT supports multiple selections. throws NotImplementedError If the SELECT does not support multiple selections.


Illustration

Note: As you have mentioned in your question I have also simulated the selection of all the items manually

  • Code Block:

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait 
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.ui import Select
    import time
    
    options = webdriver.ChromeOptions() 
    options.add_argument("start-maximized")
    options.add_argument('disable-infobars')
    driver=webdriver.Chrome(chrome_options=options, executable_path=r'C:\Utility\BrowserDrivers\chromedriver.exe')
    driver.get('https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_select_multiple')
    WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID,"iframeResult")))
    select_cars = Select(driver.find_element_by_css_selector("select[name='cars']"))
    time.sleep(5) # Timeframe to Manually select all the items
    select_cars.deselect_all()
    
  • Browser Snapshot:

deselect_all.gif

Upvotes: 0

Jack Miller
Jack Miller

Reputation: 7667

Even though I did not ask for code how to set the selection state, I'll post it anyways for anybody who encountered the same problem. This is C# code. You'll need NuGet packages Selenium.WebDriver and Selenium.Support. BrowserDriver is a member variable of my helper class which contains this method.

/// <summary>
/// Sets the selection state of <paramref name="selectElement"/>. All options specified  by <paramref name="selectedOptions"/>
/// are select, all others are unselected.
/// </summary>
/// <param name="selectElement">HTML select element</param>
/// <param name="selectedOptions">options to be selected</param>
internal void SelectStateByText(OpenQA.Selenium.Support.UI.SelectElement selectElement, params string[] selectedOptions)
{
    Assert.IsNotNull(selectElement);
    Assert.IsNotNull(selectedOptions);
    CollectionAssert.IsSubsetOf(selectedOptions, selectElement.Options.Select(o => o.Text).ToArray());
    if (!selectElement.IsMultiple)
    {
        Assert.AreEqual(1, selectedOptions.Length);
        selectElement.SelectByText(selectedOptions[0]);
    }
    else
    {
        var actions = new OpenQA.Selenium.Interactions.Actions(BrowserDriver);
        actions.KeyDown(Keys.LeftControl);
        foreach (var option in selectElement.Options)
        {
            if (selectedOptions.Contains(option.Text) && !option.Selected)
            {
                actions.Click(option);
            }
            else if (option.Selected)
            {
                actions.Click(option);
            }
        }
        actions.KeyUp(Keys.LeftControl).Build().Perform();
    }
}

Upvotes: 0

Guy
Guy

Reputation: 50899

Select class can handle multiple choice dropdown. It even checks if the dropdown is multiple choice when using DeselectAll(). From github

public void DeselectAll()
{
    if (!this.IsMultiple)
    {
        throw new InvalidOperationException("You may only deselect all options if multi-select is supported");
    }

    foreach (IWebElement option in this.Options)
    {
        SetSelected(option, false);
    }
}

private static void SetSelected(IWebElement option, bool select)
{
    bool isSelected = option.Selected;
    if ((!isSelected && select) || (isSelected && !select))
    {
         option.Click();
    }
}

When you click on the first select option without pressing the control key this option remains selected but all the other options are being deselected, so the click is actually not performed on the rest of the options since both isSelected and select are false in SetSelected.

The solution is either implement your own DeselectAll() as suggested in your question, or select the first option from the dropdown (which will automatically deselect all the other options) and then deselect this option using control.

Upvotes: 0

Related Questions