Real apps are made of lists and dropdowns, and both have a trap that lets a broken feature pass.
Dropdowns first.
A standard HTML dropdown is a <select> element, and Cypress has a command for it:
cy.get('[data-testid="status-filter"]').select('Won');
You can select by the visible label, as above, or by the underlying value. Prefer the label, because that is what the user sees and it stays meaningful when the values change.
Then assert that it took effect:
cy.get('[data-testid="status-filter"]').should('have.value', 'won');
One warning. Many modern apps do not use real <select> elements. They build a dropdown out of <div>s for styling. .select() will not work on those, and the error message can be confusing. For a custom dropdown you click it open and click the option, exactly like a user:
cy.get('[data-testid="status-filter"]').click();
cy.contains('[role="option"]', 'Won').click();
Check the HTML in the Elements tab before deciding which one you need.
Now tables, and the mistake that matters.
Here is a filter test that looks fine and proves nothing:
cy.get('[data-testid="search"]').type('Dana');
cy.contains('Dana Fox').should('be.visible');
Dana Fox is visible. The test passes. But Dana Fox was visible before you typed anything, along with everyone else. This test passes even if the filter does absolutely nothing.
A filter has two halves, and beginners test only one. It has to show what matches and hide what does not:
cy.get('[data-testid="search"]').type('Dana');
cy.get('[data-testid="contact-row"]').should('have.length', 1);
cy.contains('Dana Fox').should('be.visible');
The count is what makes it real. Now a filter that returns everything fails.
Finding one row among many. When you need to act on a specific row, find the row first, then work inside it:
cy.contains('[data-testid="contact-row"]', 'Dana Fox')
.find('[data-testid="delete"]')
.click();
cy.contains with two arguments means "the element matching this selector that contains this text". Then .find() searches inside that row. This is much safer than grabbing all delete buttons and taking one by position, which is the bug that deletes the wrong record when the sort order changes.
Remember this: for any filter, assert the count, not just that your match is visible.
Reference: .select().