ScreenshotNeo

BlogHow-to

How to Use Select and Select2 Widgets in Cypress

Learn when to use Cypress .select(), how to test Select2’s visible controls, and how to handle multiple selections and remote results.

By the ScreenshotNeo team4 October 20269 min read

Cypress .select() works with options inside a native <select>. Select2 adds a custom visible interface to a backing select that may be hidden, so for a user-path test interact with the visible widget. For a focused state test, .select(value, { force: true }) can set the hidden select directly. In either case, assert the backing value and, when the displayed result matters, the rendered label or selected chips.

How do I use Select2 with Cypress? Choose the interaction that matches what the test is meant to cover: native selection, visible Select2 behavior, or intentional backing-state setup. Remote results need retrying queries and assertions because they arrive asynchronously.

1. Select an option in a native select

Pass the option’s value or its visible text to .select(). The value assertion checks the state submitted to the application.

cy.get('[data-cy="state"]').select('MA')
cy.get('[data-cy="state"]').should('have.value', 'MA')

// You can also select by option text. The resulting value is still MA.
cy.get('[data-cy="state"]').select('Massachusetts')
cy.get('[data-cy="state"]').should('have.value', 'MA')

Cypress documents .select() for selecting an option within a <select>, by value or text. Use a selector that uniquely identifies the intended control. Prefer an application-owned attribute such as data-cy where available over selectors tied to incidental markup. See the Cypress .select() documentation and Cypress selector best practices.

2. Understand why Select2 changes the Cypress workflow

Select2 decorates a select element with a custom interface. The original select may be hidden while Select2 renders its own selection area, search field, and result rows. Cypress checks that an action target is actionable; a hidden backing select can therefore fail a normal .select() call.

That failure is a clue to choose the right test path. If the purpose is to verify what a user can do, click the visible widget and select a displayed result. If the purpose is to set a state efficiently in a focused test, force-select the backing element and verify the outcome. Forcing skips Cypress’s normal actionability safeguard, so it does not demonstrate that the visible widget can be used. See the Cypress interaction guide and Select2 documentation.

3. Test Select2 through its visible interface

Open the widget, type into its search input if needed, click the matching result, then assert both the backing value and the rendered label. The selectors below are illustrative; actual Select2 markup and accessible names can vary by configuration and version. Inspect your app’s rendered DOM and scope queries to the intended widget.

// Example app markup: a widget wrapper and a backing select.
cy.get('[data-cy="state-select2"]').click()
cy.get('.select2-container--open .select2-search__field')
  .should('be.visible')
  .type('Massachusetts')
cy.contains('.select2-results__option', 'Massachusetts')
  .should('be.visible')
  .click()

cy.get('[data-cy="state"]').should('have.value', 'MA')
cy.get('[data-cy="state-select2"] .select2-selection__rendered')
  .should('contain', 'Massachusetts')

A search input may be rendered outside the widget wrapper, often in an opened dropdown container. In that case, scope to the open Select2 container or another stable app-owned relationship rather than assuming the search field is a descendant of the original control. If the application offers accessible labels or stable test attributes on the rendered interface, prefer those. Cypress recommends data selectors for resilient tests; generated Select2 class names can change with markup or version.

4. Set a hidden Select2 value directly when appropriate

For a focused test that needs a selected state but is not testing the visible interaction, force the backing select. Verify the value and visible label so the test catches a mismatch between the native state and the rendered widget.

cy.get('[data-cy="state"]').select('MA', { force: true })
cy.get('[data-cy="state"]').should('have.value', 'MA')
cy.get('#select2-favorite-state-container')
  .should('have.text', 'Massachusetts')

The generated container ID above is only an example. Use the actual rendered label selector in your app. If the label does not update after forcing the select, confirm that the correct Select2 instance decorates the element and that the expected change event reaches it.

5. Handle multiple selection

Select2 supports multiple values when the underlying select has the multiple attribute. Cypress can select several option values at once. The backing value is an array; visible-path tests should also check the selected chips or labels that users see.

// Direct setup for a focused state test
cy.get('[data-cy="states"]').select(['MA', 'VT'], { force: true })
cy.get('[data-cy="states"]')
  .invoke('val')
  .should('deep.equal', ['MA', 'VT'])

// For a visible interaction test, open the widget and choose each result.
cy.get('[data-cy="states-select2"]').click()
cy.contains('.select2-results__option', 'Massachusetts').click()
cy.get('[data-cy="states-select2"]').click()
cy.contains('.select2-results__option', 'Vermont').click()
cy.get('[data-cy="states"]').invoke('val').should('deep.equal', ['MA', 'VT'])
cy.get('[data-cy="states-select2"] .select2-selection__rendered')
  .should('contain', 'Massachusetts')
  .and('contain', 'Vermont')

Adapt the visible sequence to your widget configuration: some controls remain open after a selection, and some close. Select2’s selection documentation describes its single and multiple selection behavior.

6. Change values programmatically and notify Select2

When application or test code sets a value with jQuery, trigger change so Select2 and other listeners can update. Select2 also documents the narrower change.select2 event when only Select2 should be notified.

// Application-side jQuery example:
$('#mySelect2').val('1').trigger('change')

// Notify Select2 specifically:
$('#mySelect2').val('1').trigger('change.select2')

// Observe a Select2 selection event:
$('#mySelect2').on('select2:select', (e) => {
  const selected = e.params.data
  console.log(selected.id, selected.text)
})

In Cypress, invoking plugin methods can help inspect or set state when the page exposes jQuery and Select2. Treat that as setup or inspection, not proof that a user can operate the rendered control. Select2 relays public events on the attached select; its events documentation describes event payloads and scoped change events.

7. Test AJAX-loaded Select2 results

Remote Select2 results are asynchronous. A remote option may not exist as an <option> before it has been selected once, so do not assume the backing select contains every possible result at page load. Open and search the visible widget, then let Cypress retry queries and assertions until the result appears.

cy.get('[data-cy="city-select2"]').click()
cy.get('.select2-container--open .select2-search__field')
  .should('be.visible')
  .type('San Francisco')

// contains() retries while Cypress waits for matching content
cy.contains('.select2-results__option', 'San Francisco', { timeout: 10000 })
  .should('be.visible')
  .click()

cy.get('[data-cy="city"]').should('have.value', 'sf')
cy.get('[data-cy="city-select2"] .select2-selection__rendered')
  .should('contain', 'San Francisco')

Set a longer timeout on the specific result query only when the expected network and application behavior needs it. Avoid fixed sleeps: they can make a fast run slower and still fail on a slower response. Cypress retries queries and assertions within their timeout rules. See Select2 AJAX data sources and Cypress retry-ability.

8. Choose selectors and assertions deliberately

Test goal Interaction Useful assertion
Native select behavior .select(valueOrText) Backing select has expected value
Visible Select2 user flow Open, search or choose a result Backing value and displayed label or chips
Fast state setup .select(value, { force: true }) Backing value; rendered label if relevant
Remote result behavior Open/search, query for result Retrying result and selected-state assertions
Application response to selection Use the path relevant to the test Observable application behavior, such as updated dependent content

Use selectors that identify one intended control. Cypress commands that type into an input require a single target; an unscoped Select2 search-field query can match multiple widgets. Scope to the open dropdown or a stable widget relationship. Assert the state the application actually depends on, not just that a click completed.

9. Troubleshooting

Symptom Likely cause Fix
.select() says the element is not visible or actionable Select2 hides the backing native select. Exercise the visible Select2 interface, or intentionally use { force: true } for state setup and verify the result.
More than one element matches the search field Multiple Select2 controls or hidden/visible search inputs match a broad selector. Scope the query to the open container and ensure it resolves to one field.
The result query finds no option AJAX has not returned yet, the search term is wrong, or the test targets a result before opening/searching. Open the widget, type the expected term, and use a retrying query/assertion with a suitable timeout. Check the app’s network response and result text.
The backing value changes but the label does not The Select2 widget was not notified, the wrong control was targeted, or the display assertion uses a stale/generated selector. For programmatic jQuery changes trigger change (or change.select2 when appropriate); verify the attached select and inspect current markup.
A remote value is missing from the DOM before selection Select2 creates an option for a remote item when it is first selected. Assert against the loaded result interface, or first select the remote result before expecting a backing option.
A multi-select assertion has the wrong shape The selected values are an array, and a test may be asserting a string or a different ordering/representation. Inspect .invoke('val') and assert the expected array and visible chips.
A forced selection passes while a user flow is broken Force bypasses actionability checks and does not operate the rendered control. Add a separate visible-interaction test for the user path.

10. Performance, reliability, and test cost

  • Keep the test focused. Direct selection is useful for setup when the UI interaction is outside the scenario; reserve visible-path coverage for tests whose purpose includes operating Select2.
  • Prefer retrying queries over sleeps. They wait for the expected condition and do not impose a fixed delay on every run.
  • Use stable selectors. App-owned data attributes are less coupled to Select2’s generated markup than plugin classes or generated IDs.
  • Assert both sides when they can diverge. The backing value matters to form submission and app logic; the rendered label matters to user-visible feedback.
  • Account for network-dependent tests. AJAX-backed options add response-time variability. Query for a specific expected result and tune its timeout to the application’s real behavior rather than adding a blanket wait.

11. Or skip the browser setup

If your next task is capturing the page for a visual check or report, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; the API call below follows the product’s documented basic request pattern. See the ScreenshotNeo API documentation for options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents use screenshot, page-info, and PDF-capture tools.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.

Start with 1,000 free screenshots a month, with no card required.

12. FAQ

Can I select by visible label?

Yes. Native Cypress .select() accepts option text as well as option value. The resulting selected value is the option’s actual value.

Should every Select2 test use force: true?

No. Use it for intentional hidden-select state setup. Use the visible control when the test should cover the interaction a user performs.

Why can a remote option be absent from the native select?

Select2 does not necessarily create an option node for a remote result until that result is selected for the first time.

Which state should I assert?

Assert the backing value when form or application logic depends on it. Also assert the visible label or chips when user feedback is part of the behavior under test.