---
title: "Working with Select elements and Select2 widgets in Cypress"
url: "https://www.cypress.io/blog/working-with-select-elements-and-select2-widgets-in-cypress"
description: "This blog post shows how to control native <select> HTML elements from Cypress tests. We will also look at how to work with a very popular wrapper library called Select2, that supplants the native <select> elements with an additional HTML markup."
---

[Blog](/blog/)[Education: Tutorials](/blog/tag/tutorials)

# Working with Select elements and Select2 widgets in Cypress

March 20, 2020

•

By Gleb Bahmutov

This blog post shows how to control native `<select>` HTML elements from Cypress tests. We will also look at how to work with a very popular wrapper library called [Select2](https://select2.org/?ref=cypress-io.ghost.io), that supplants the native `<select>` elements with an additional HTML markup.

**Note:** you can find the source code for this post in the recipe ["Select widgets"](https://github.com/cypress-io/cypress-example-recipes?ref=cypress-io.ghost.io#testing-the-dom).

## Single value select element

Let's take a page where a user selects a single state from a list of choices. Maybe it is their home state, or the state they would like to avoid where their nemesis lives.

```html
<label for="my-state">
  Pick one state
</label>
<select id="my-state" name="my-state" style="width: 50%">
  <optgroup label="Alaskan/Hawaiian Time Zone">
    <option value="AK">Alaska</option>
    <option value="HI">Hawaii</option>
  </optgroup>
  <optgroup label="Pacific Time Zone">
    <option value="CA">California</option>
    <option value="NV">Nevada</option>
    ...
</select>
```

Modern browsers render the native `<select>` pretty well. Here is Chrome 80 showing the list of states grouped by timezone.

The select element in Chrome 80

We can set the selected state from a Cypress test using the [.select](https://on.cypress.io/select?ref=cypress-io.ghost.io) command.

```js
beforeEach(() => {
  cy.visit('index.html')
})

describe('HTML select element', () => {
  context('single value', () => {
    it('sets MA', () => {
      // https://on.cypress.io/select
      // set using value
      // <option value="MA">Massachusetts</option>
      cy.get('#my-state').select('MA')

      // confirm the selected value
      cy.get('#my-state').should('have.value', 'MA')
    })
  })
})
```

_Note:_ to skip repeating the same code the following test snippets will omit `beforeEach` and `describe` blocks.

Single select Cypress test in action

Cypress command [.select](https://on.cypress.io/select?ref=cypress-io.ghost.io) allows one to select an option using its value or its text content. The next test selects the option with text content "Massachusetts".

```js
it('sets Massachusetts', () => {
  // https://on.cypress.io/select
  // set using text
  // <option value="MA">Massachusetts</option>
  cy.get('#my-state').select('Massachusetts')

  // confirm the selected value
  cy.get('#my-state').should('have.value', 'MA')
})
```

## Selecting multiple values

HTML allows a single `<select>` element to have multiple options to be picked

```html
<label for="my-states">
  Pick several states
</label>
<select id="my-states" name="my-states" multiple style="width: 50%">
  ...
</select>
```

The test works the same way - we just need to be careful when checking the selected value - we need to use deep equality to compare the two arrays.

```js
it('adds several states', () => {
  // https://on.cypress.io/select
  // select multiple values by passing an array
  cy.get('#my-states').select(['MA', 'VT', 'CT'])

  // confirm the selected value - note that the values are sorted
  // and because it is an array we need to use deep equality
  // against the yielded list from ".invoke('val')"
  cy.get('#my-states').invoke('val').should('deep.equal', ['CT', 'MA', 'VT'])
})
```

The test works and picks 3 states

Multiple selections

**Tip:** note the blue outline around the `<select>` element after the test is finished. The element still has the browser focus - which can surprise you while interacting with the Cypress GUI. For example, when pressing "r" to re-run the tests, you will instead change the state to "Rhode Island"!

Clicking "r" after the test is finished ... selects "Rhode Island" (and nobody wants that)

Thus, this particular test is nice to end with blurring the `<select>` element

```js
it('adds several states', () => {
  // https://on.cypress.io/select
  // select multiple values by passing an array
  cy.get('#my-states').select(['MA', 'VT', 'CT'])

  // confirm the selected value - note that the values are sorted
  // and because it is an array we need to use deep equality
  // against the yielded list from ".invoke('val')"
  cy.get('#my-states').invoke('val').should('deep.equal', ['CT', 'MA', 'VT'])

  // remove the focus from <select> element
  cy.get('#my-states').blur()
})
```

The focus is removed from the select element

## Single value using Select2

The standard `<select>` element is ok, but the wrapper libraries like [Select2](https://select2.org/?ref=cypress-io.ghost.io) make it even better. First, you need to load the jQuery and the Select2 libraries and initialize the widget like this:

```html
<head>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"
    integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
    crossorigin="anonymous"></script>
  <link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet" />
  <script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
</head>
<body>
  <label for="favorite-state">
    Pick your favorite state
  </label>
  <select class="js-example-basic-single" id="favorite-state"   style="width: 50%">
    ...
  </select>
  <script src="app.js"></script>
</body>
```

The "app.js" file creates the HTML widget that shows up in place of the `<select>` element.

```js
// app.js
$(document).ready(function () {
  $('.js-example-basic-single').select2()
})
```

The Select2 widget to me looks nicer than the stock `<select>` element, plus it has autocomplete input that is helpful.

Select2 single value widget

If we write a Cypress test to set the single value against the `<select>` element wrapped using Select2 ... it will fail.

```js
it('selects Massachusetts', () => {
  cy.log('--- Force select ---')

  // https://on.cypress.io/select
  cy.get('#favorite-state').select('MA')

  // confirm the value of the selected element
  cy.get('#favorite-state').should('have.value', 'MA')
})
```

Failed test because Select2 markup covers the original <select> element

When Select2 renders itself, it hides the original `<select>` element, leaving just a single pixel-high rectangle. Cypress checks if the element is visible before selecting a value, and rejects such barely visible elements, because the user could not select an element this way.

The HTML markup shows why Cypress refuses to act on the original <select> element

We can force Cypress to by-pass its built-in [.select checks](https://on.cypress.io/select?ref=cypress-io.ghost.io#Rules) by using `force: true` option.

```js
it('selects Massachusetts', () => {
  cy.log('--- Force select ---')

  // https://on.cypress.io/select
  cy.get('#favorite-state').select('MA', { force: true })

  // confirm the value of the selected element
  cy.get('#favorite-state').should('have.value', 'MA')
})
```

The test forces the element to select a value

We have the `<select>` element with the right value - but does the Select2 HTML wrapper show the right state's name? To see how the widget renders the selection, inspect its markup using the DevTools Element panel after the test has finished.

Find the element using the DevTools Elements panel

You can find the same selector even faster by using the [Cypress Selector Playground](https://on.cypress.io/selector-playground?ref=cypress-io.ghost.io) which tries to pick a [good selector](https://on.cypress.io/best-practices?ref=cypress-io.ghost.io#Selecting-Elements) for a visible element.

Picking the element using the Cypress Selector Playground tool

After you click the "Copy" button, you have the entire `cy.get('#select2-favorite-state-container')` command in your clipboard - paste it in your test and go!

```js
it('selects Massachusetts', () => {
  cy.log('--- Force select ---')

  // https://on.cypress.io/select
  // avoid error message that select element is covered
  // by the rendered Select2 widget by using "force: true" option
  cy.get('#favorite-state').select('MA', { force: true })

  // confirm the value of the selected element
  cy.get('#favorite-state').should('have.value', 'MA')

  // confirm Select2 widget renders the state name
  cy.get('#select2-favorite-state-container').should('have.text', 'Massachusetts')
})
```

Confirm the text shown by the Select2 widget

## Selecting single value by typing

Let's select a state by typing part of its name. Here is where knowing the HTML markup the Select2 creates is crucial in order to send the right commands from the test. Luckily, we can always inspect the HTML structure using the DevTools.

First, notice that the user has to click the widget before the the input element appears.

User first clicks the Select2 widget

Let's click from our test too, but we can't click on the original `<select>` element - instead let's click on the HTML element Select2 adds to the page. We can use the Cypress Selector Playground tool just like before to pick the selector and the command and copy paste them into the test

```js
it('selects Massachusetts by typing', () => {
  cy.log('--- Pick state by typing ---')
  cy.get('#select2-favorite-state-container').click()
})
```

The test opens the input widget, and we can check the selector for the input element to type into.

Selector for the input element created by Select2 widget

We could copy the selector `cy.get('.select2-search')` suggested by the tool BUT this is very fragile. Notice in this case the selector does not have _anything_ connecting it to the original `<select class="js-example-basic-single" id="favorite-state">` element. If our page has several Select2 widgets, the `cy.get('.select2-search')` command would return _all of them_ breaking the test, because Cypress refuses to type into multiple elements at once.

In situations likes this, you need to look closely at the HTML markup to see if there is anything specific that ties the widget back to its original element.

Inspecting the Select2 HTML markup

Notice the Select2 widget has an attribute that has a part of the original `<select>` name - the `aria-control=select2-favorite-state-results`. We can use this attribute as a selector!

```js
it('selects Massachusetts by typing', () => {
  cy.log('--- Pick state by typing ---')
  cy.get('#select2-favorite-state-container').click()

  cy.get('input[aria-controls="select2-favorite-state-results"]').type('Mass{enter}')
})
```

Typing in the selected element

We can extend the test to confirm the selected value and the shown text are correct

```js
it('selects Massachusetts by typing', () => {
  cy.log('--- Pick state by typing ---')
  // first, open the widget
  cy.get('#select2-favorite-state-container').click()
  // then type into the widget's input element
  // use custom selector to handle cases when there are multiple widgets
  // on the page
  cy.get('input[aria-controls="select2-favorite-state-results"]').type('Mass{enter}')

  // confirm the value of the selected element
  cy.get('#favorite-state').should('have.value', 'MA')

  // confirm Select2 widget renders the state name
  cy.get('#select2-favorite-state-container').should('have.text', 'Massachusetts')
})
```

Just to show the widget in action, you can delay typing each character using [.type](https://on.cypress.io/type?ref=cypress-io.ghost.io) command's options:

```js
cy.get('input[aria-controls="select2-favorite-state-results"]')
  .type('Mass{enter}', {
     delay: 500,
  })
```

cy.type with delay after each character

## Multiple values using Select2

Let's move to the next situation: when Select2 widget wraps a `<select>` element that allows picking multiple values. The `<select>` HTML markup is:

```html
<label for="states">
  Pick several states
</label>
<select class="js-example-basic-multiple" id="states"
  name="states" multiple="multiple" style="width: 90%">
  ...
</select>
```

We can set the values directly on the `select#states` element from the test:

```js
it('adds several states', () => {
  // https://on.cypress.io/select
  // select multiple values by passing an array
  // again, have to use "force": "true" because the actual
  // select is covered by Select2 widget
  cy.get('#states').select(['MA', 'VT', 'CT'], { force: true })

  // confirm the selected value - note that the values are sorted
  // and because it is an array we need to use deep equality
  // against the yielded list from ".invoke('val')"
  cy.get('#states').invoke('val').should('deep.equal', ['CT', 'MA', 'VT'])
})
```

The test selects multiple values

How do we confirm the "pills" displayed by the Select2 widget - the `x Connecticut`, `x Massachusetts`, `x Vermont` we see there? Unfortunately, Cypress Selector Playground is really geared towards selecting individual elements, as the next screenshot shows

Selector Playground suggest the selector for each element

Let's open DevTools console and look at the markup using the Elements panel.

Markup for <select> element is followed by Select2 widget

Our original element `select#states` is immediately followed by `.select2` element that contains the list items with every selected state. The combined selector for this would be `#states + .select2 .select2-selection__choice` - first it finds an element with id `states`, then the next element (CSS `+` operator) with class `select2`, then inside an element with class `` `select2-selection__choice` ``. Let's write our test:

```js
it('adds several states', () => {
  // https://on.cypress.io/select
  // select multiple values by passing an array
  // again, have to use "force": "true" because the actual
  // select is covered by Select2 widget
  cy.get('#states').select(['MA', 'VT', 'CT'], { force: true })

  // confirm the selected value - note that the values are sorted
  // and because it is an array we need to use deep equality
  // against the yielded list from ".invoke('val')"
  cy.get('#states').invoke('val').should('deep.equal', ['CT', 'MA', 'VT'])

  // confirm Select2 widget renders the 3 state names
  cy.get('#states + .select2 .select2-selection__choice')
  // use ".should(cb)" to confirm the displayed values
  // https://on.cypress.io/should#Function
  .should((list) => {
    expect(list[0].title).to.equal('Connecticut')
    expect(list[1].title).to.equal('Massachusetts')
    expect(list[2].title).to.equal('Vermont')
  })
})
```

The test confirms that these New England states are indeed what we wanted to select.

Three selected states

What if we want to find states by typing, not by setting them by value? Let's type using the markup shown by the Elements panel. The test delays each character by 500ms for clarity

```js
it('adds several states by typing', () => {
  // Select2 HTML widget is inserted after the corresponding <select> element
  // thus we can find it using " + " CSS selector
  cy.get('#states + .select2').click()
  // after we click on the Select2 widget, the search drop down and input appear
  .find('.select2-search')
  // type parts of the states' names, just like a real user would
  .type('Verm{enter}Mass{enter}Conn{enter}', { delay: 500 })

  // confirm the selected value - note that the values are sorted
  // and because it is an array we need to use deep equality
  // against the yielded list from ".invoke('val')"
  cy.get('#states').invoke('val').should('deep.equal', ['CT', 'MA', 'VT'])
})
```

Typing multiple states and asserting the selection

## Removing selected item

What if the user selects a few items, then removes one of them? Can we test that? Again, let's look at the markup after selecting a few items.

Remove an item markup

Each Select2 item has a span with class `select2-selection__choice__remove`. It is a mouthful, but we can use it to remove an item from the test.

```js
it('removes state', () => {
  cy.get('#states + .select2').click()
    .find('.select2-search')
    .type('Verm{enter}Mass{enter}Conn{enter}')

  // confirm 3 states are selected
  cy.get('#states').invoke('val')
    .should('deep.equal', ['CT', 'MA', 'VT'])

  // remove Connecticut, they don't have a major league team anyway
  cy.get('#states + .select2')
    .contains('.select2-selection__choice', 'Connecticut')
    .find('.select2-selection__choice__remove').click()

  // when removing an item, Select2 automatically expands choices
  // we can close them by pressing "Enter"
  cy.get('#states + .select2 .select2-search')
    .type('{enter}')

  // confirm the remaining selections
  cy.get('#states').invoke('val').should('deep.equal', ['MA', 'VT'])
})
```

The test is fast - blink, and you will miss it - but you can still see a state being added and deleted correctly.

Removing one of the selections by clicking test

## Programmatic control

Select2 allow the application code to retrieve selected items [programmatically](https://select2.org/programmatic-control/retrieving-selections?ref=cypress-io.ghost.io). For example, you can get currently selected items by calling method `select2` with argument `data`.

```js
$('#mySelect2').select2('data');
```

Cypress tests can directly invoke this method from the test - because the test runs inside the browser.

```js
it('returns selected items', () => {
  cy.get('#states').select(['MA', 'VT', 'CT'], { force: true })
  // https://select2.org/programmatic-control/retrieving-selections
  cy.get('#states').invoke('select2', 'data')
})
```

Once the test finishes, click on the `INVOKE` command to see the yielded value - it is a list of selected states (with extra properties added by Select2 code)

The selected items returned by the `select2('data')` call

From each object, let's pick the `text` fields and assert the expected states are selected. Cypress [bundles Lodash](https://on.cypress.io/bundled-tools?ref=cypress-io.ghost.io) that allows us to map each object to its property elegantly.

```js
it('returns selected items', () => {
  cy.get('#states').select(['MA', 'VT', 'CT'], { force: true })
  // https://select2.org/programmatic-control/retrieving-selections
  cy.get('#states').invoke('select2', 'data')
    .should((list) => {
      const names = Cypress._.map(list, 'text')
      expect(names).to.deep.equal([
        'Connecticut', 
        'Massachusetts', 
        'Vermont'
      ])
    })
})
```

Asking Select2 for selected items and confirming the list of state names

## Fetched Data

Select2 allows to load choices from a remote server using [an Ajax](https://select2.org/data-sources/ajax?ref=cypress-io.ghost.io) call. Here is the initialization code fetching a list of users:

```js
$('.js-example-remote-data').select2({
  placeholder: 'Pick a user',
  ajax: {
    url: 'https://jsonplaceholder.cypress.io/users',
    dataType: 'json',
    delay: 250,
    processResults (data) {
      return {
        results: $.map(data, function (item) {
          return {
            text: item.name,
            id: item.id,
          }
        }),
      }
    },
  },
})
```

Our test should be careful not limit the element search to the initial empty list of choices. For example, this test WILL NOT work

```js
it('selects a value - WILL NOT WORK, JUST A DEMO', () => {
  // clicking on the container starts Ajax call
  cy.get('#select2-user-container').click()

  // we know the choice elements have this class
  cy.get('.select2-results__option')
    // so let's try finding the element with the text
    // WILL NOT WORK
    .contains('Leanne Graham').click()

  // confirm the right user is found
  cy.get('#user').should('have.value', '1')
  cy.get('#select2-user-container').should('have.text', 'Leanne Graham')
})
```

The above test fails - even though we can see the user with the name we are looking for.

Test fails despite showing the right user name in the list

The reason for the failure is described in our [Retry-ability Guide](https://on.cypress.io/retry-ability?ref=cypress-io.ghost.io). By splitting the command `cy.get(...).contains(...)` the test "falls" into a trap. It finds the loading placeholder element, and then only searches that part of the DOM, while the true list is created in a _different_ DOM element. You can see the problem by hovering over the "get .select2-results\_\_option" command.

.get command shows the element we are searching

There are several solutions to this problem, all shown in [cypress-example-recipes](https://github.com/cypress-io/cypress-example-recipes?ref=cypress-io.ghost.io) repository. Perhaps the simplest one is to use a _single_ command to query the element by text.

```js
it('selects a value by retrying', () => {
  // clicking on the container starts Ajax call
  cy.get('#select2-user-container').click()

  // we need to retry getting the option until it is found
  // then we can click it. To retry finding the element with text
  // we should use https://on.cypress.io/contains
  cy.contains('.select2-results__option', 'Leanne Graham').click()

  // confirm the right user is found
  cy.get('#user').should('have.value', '1')
  cy.get('#select2-user-container').should('have.text', 'Leanne Graham')
})
```

The [cy.contains](https://on.cypress.io/contains?ref=cypress-io.ghost.io) command retries until Select2 fetches and displays the list of options and an option with text "Leanne Graham" is present.

## Conclusions

If you are working with a library that wraps a native HTML element with a widget, like Select2 does, it is important to know the additional markup. The test has to interact with the new markup to control the widget and assert it is wired correctly. In this post, we have looked at the native `<select>` element and its wrapper Select2 and how Cypress end-to-end tests can work in both situations.

-   [cy.select](https://on.cypress.io/select?ref=cypress-io.ghost.io) command
-   ["Select widgets"](https://github.com/cypress-io/cypress-example-recipes?ref=cypress-io.ghost.io#testing-the-dom) recipe has the source code for this blog post