Forms are one of the most heavily tested parts of any application. This lesson covers assertions for input values, checkbox and radio state, disabled fields, and validation messaging.
Asserting on Form State
Beyond simply typing into fields, real form tests need to confirm the resulting state: does an input hold the expected value, is a checkbox checked, is a submit button correctly disabled until required fields are filled.
have.value checks an <input>, <textarea>, or <select>'s current value.
be.checked/not.be.checked applies to checkboxes and radio buttons.
be.disabled/not.be.disabled checks whether a field or button is interactive.
have.attr('required') confirms a field is marked required in the markup.
Form Assertions Cheat Sheet
Common checks for verifying form field state after user interaction.
Goal
Command
Input value
.should('have.value', 'text')
Checkbox checked
.should('be.checked')
Checkbox unchecked
.should('not.be.checked')
Field disabled
.should('be.disabled')
Field enabled
.should('not.be.disabled')
Required attribute
.should('have.attr', 'required')
Validation error shown
.should('contain', 'This field is required')
Field marked invalid
.should('have.class', 'is-invalid')
Testing Client-Side Validation Messages
Client-side validation typically shows an error message and/or an "invalid" styling class after a failed submission attempt. Testing this end-to-end (rather than only via unit tests) confirms the whole feedback loop, form submit, validation trigger, message render, works correctly together.
cy.get('[data-cy="signup-form"] [data-cy="submit"]').click();
cy.get('[data-cy="email-error"]')
.should('be.visible')
.and('contain', 'Email is required');
cy.get('[data-cy="email"]').should('have.class', 'is-invalid');
Testing Progressive Enable/Disable Behavior
Many forms disable a submit button until all required fields are valid. Testing both states, disabled before completion, enabled after, confirms the enabling logic itself, not just the final successful submission.
This pattern verifies the button's enabled state transitions correctly as required fields are completed.
Native Browser Validation vs. Custom Validation
Native HTML5 validation (via the required or pattern attributes) shows browser-native tooltips that Cypress cannot easily inspect as regular DOM content. Many applications instead implement custom JavaScript validation with visible error elements, which is far easier and more reliable to assert on directly.
Prefer testing custom, DOM-rendered validation messages when your app implements them.
For native browser validation, check the input's validity property via .then() if a direct assertion is truly required.
Confirm which validation approach your application actually uses before writing the test.
Common Mistakes
Trying to assert directly on native browser validation tooltip text as if it were normal DOM content.
Only testing the successful submission path and never asserting on validation error states.
Forgetting to test that a submit button re-enables correctly once previously invalid fields are fixed.
Asserting on a value immediately after .type() without confirming the type action actually completed first.
Key Takeaways
Form assertions confirm the resulting state after interaction, not just that an action command ran.
have.value, be.checked, and be.disabled cover most common form field state checks.
Testing validation error messages and progressive button enabling closes real coverage gaps.
Native browser validation tooltips need a different approach than custom, DOM-rendered validation.
Pro Tip
Test the disabled-to-enabled transition of a submit button explicitly, not just the end state, this catches a surprisingly common bug where a button becomes permanently enabled after the first valid keystroke, even if a field is cleared again afterward.
You now know how to assert on form state and validation. Next, learn Cypress Waiting to understand how timing and synchronization work.