How to Fill and Submit Forms in Cypress
Fill text fields, select options, submit forms, and assert the result in Cypress, with examples for validation, network requests, and keyboard submission.
To fill and submit a form in Cypress, visit the page, query controls with stable selectors, enter or select values, submit using the interaction you want to test, and assert the visible result. For request-driven forms, register a cy.intercept() before submission and wait for its alias. A completed click or typing command alone does not prove the application handled the form correctly.
1. Fill and submit a basic form
This example assumes the application has a contact page with fields named email and message, a submit button, and a status message after success. Replace those selectors and expected text with your application’s actual markup and behavior.
describe('contact form', () => {
it('submits a message and shows confirmation', () => {
cy.visit('/contact')
cy.get('form').within(() => {
cy.get('[name="email"]').type('reader@example.test')
cy.get('[name="message"]').type('Please send the details.')
cy.get('button[type="submit"]').click()
})
cy.get('[role="status"]')
.should('be.visible')
.and('contain.text', 'Thank you')
})
})
cy.get() queries the document. Inside .within(), queries are scoped to the selected form, which helps avoid accidentally interacting with similarly named controls elsewhere on the page. Prefer stable semantics such as a field’s name, a label-associated selector, or an application-specific test attribute. [Cypress cy.get()]
2. Choose the right command for each control
Use the Cypress command that matches the control type. Assert checked or selected state when that state is an important part of the behavior.
| Control | Example | Notes |
|---|---|---|
| Text input or textarea | .type('value') |
Use .clear() first when replacing existing text. |
| Checkbox | .check() or .uncheck() |
Assert the expected checked state if relevant. |
| Radio control | .check() |
Target the intended option specifically. |
| Native select | .select('option value') |
Use a value or label that exists in the select. |
| Submit | .click() |
Exercises the visible submit control and its actionability. |
cy.get('[name="email"]').clear().type('new@example.test')
cy.get('[name="updates"]').check()
cy.get('[name="contact-method"][value="email"]').check()
cy.get('[name="country"]').select('CA')
cy.get('[name="updates"]').should('be.checked')
cy.get('[name="country"]').should('have.value', 'CA')
These commands and selectors are examples; adapt them to the real control types and values. Cypress’s command reference covers type() and its interaction commands.
3. Submit by clicking or pressing Enter
Click the submit button when the test should cover the primary visible action. Cypress waits for actionability conditions before clicking, including that the target is not hidden, covered, disabled, or animating. This helps reveal issues that a forced click could conceal. [Cypress interaction behavior]
cy.get('button[type="submit"]').click()
Test Enter-key submission when keyboard submission is part of the intended behavior. Browser implicit submission depends on the form structure and submit control; it is not guaranteed for every arrangement. Cypress documents cases where typing Enter in a form input submits the form and fires submit behavior. If the form handler prevents the default action, navigation or native submission may not happen. [Cypress cy.type()]
cy.get('[name="password"]').type('example{enter}')
cy.get('[role="status"]').should('contain.text', 'Signed in')
Keep the final assertion: it shows that the intended result occurred, whether submission was triggered by a click or the keyboard.
4. Synchronize with a form request
When submission sends a request, register the intercept before the action, then wait on the matching alias. This makes the test wait for the event it needs instead of guessing with a fixed delay.
cy.intercept('POST', '/api/contact').as('submitContact')
cy.visit('/contact')
cy.get('form').within(() => {
cy.get('[name="email"]').type('reader@example.test')
cy.get('[name="message"]').type('Please send the details.')
cy.get('button[type="submit"]').click()
})
cy.wait('@submitContact')
cy.get('[role="status"]')
.should('be.visible')
.and('contain.text', 'Thank you')
Replace the method, route, fields, and outcome with the actual application flow. Waiting for the request confirms that the matching request completed; assert the user-visible result too. Cypress’s click documentation shows registering an intercept before the click and waiting for the alias.
5. Test validation and unsuccessful submissions
A useful form test checks both a successful submission and important invalid-input behavior. Assert the validation response users should see, such as an error message or an invalid field state. Keep assertions tied to the application contract rather than assuming every form uses the same copy or markup.
cy.visit('/contact')
cy.get('[name="email"]').type('not-an-email')
cy.get('[name="message"]').type('A message')
cy.get('button[type="submit"]').click()
cy.get('[role="alert"]')
.should('be.visible')
.and('contain.text', 'valid email')
cy.get('[name="email"]').should('have.attr', 'aria-invalid', 'true')
The selectors and expected error are illustrative. If the app blocks submission through native browser validation, test the resulting invalid state your app exposes. For server-side validation, intercept the request if appropriate and assert the resulting error shown by the UI.
6. Selectors, retries, and assertions
- Choose selectors that remain stable when styling changes: field names, associated labels, or dedicated test attributes are usually clearer than positional selectors.
- Scope related controls with
.within()when the page has multiple forms or repeated controls. - Cypress retries queries and assertions while waiting for their requested conditions. Use assertions such as
.should('be.visible')or.should('contain.text', ...)to describe the expected state. [Cypress assertions] - Do not use
{ force: true }to get around an unexpected covered, hidden, disabled, or moving element unless bypassing normal actionability is specifically what the test intends to cover. - Assert a meaningful outcome: confirmation, navigation, updated data, or validation feedback. A command completing successfully does not establish that the form’s behavior is correct.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The query finds the wrong field or multiple controls | The selector is too broad or the page has repeated forms. | Use a stable, specific selector and scope it with .within(). |
| Cypress says the button is covered, hidden, disabled, or animating | The control is not actionable in its current state. | Check overlays, loading state, visibility, and whether the form is ready. Avoid forcing the click unless that bypass is intentional. |
| The click succeeds but the test still passes without the expected result | The test asserts only that an interaction happened. | Add an assertion for the resulting UI state or behavior. |
| The test is flaky after submission | It relies on elapsed time rather than the request or UI condition. | Intercept the known request before submitting and wait for its alias; assert the resulting state. |
| Pressing Enter does not submit | Implicit submission depends on form structure and the submit control. | Check the form’s actual structure and test Enter only if it is the intended interaction. Use an explicit click for the visible button path. |
| A select or checkbox assertion fails | The target value is wrong, the selector matches a different control, or the interaction did not set the expected state. | Verify the option value and selector, then assert the selected value or checked state that matters. |
| The form shows an error after apparently valid input | The sample data may not satisfy application-specific validation, or the server rejected it. | Use valid test data for the success path and separately assert expected validation behavior for invalid data. |
8. Performance, reliability, and test cost
Keep the test focused on the form behavior: use direct selectors, wait for a relevant request or retried assertion, and avoid arbitrary sleeps. Fixed delays slow a suite and can still be too short under slower conditions. Cypress’s retry behavior and request aliases provide condition-based synchronization. Reliable assertions also make failures easier to diagnose because they state what result was missing.
For tests that send real data to external services, consider the effects of repeated submissions and use an appropriate test environment or controlled test data. The Cypress sources cited here describe command behavior, not a universal runtime or cost benchmark, so execution time and service costs depend on the app and its test setup.
9. Or skip the browser setup
If the task is capturing the submitted form page for a visual record, a screenshot API can avoid setting up browser automation for that capture. ScreenshotNeo is a website screenshot API and MCP server for developers. It accepts a URL in one GET request and can return PNG, JPEG, WebP, or PDF. This does not replace Cypress interaction and assertions when you need to test form behavior.
Here is the one-call example. See the ScreenshotNeo API docs for request 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}`);
- Cookie banners are accepted before capture, and 60+ known consent platforms, newsletter popups, and chat widgets can be removed. Each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status.
- An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
- The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Should I use cy.get() or .within()?
Use cy.get() to query the document. Use .within() to limit related queries to a selected form or container.
Should I submit with a click or Enter?
Use a click to test the visible submit control. Use Enter when keyboard submission is part of the behavior you need to cover and the form structure supports implicit submission.
Does cy.wait() replace the final assertion?
No. Waiting on a request alias synchronizes the test with that request. Assert the page or application state the user should see as well.


