Writing disabled-state tests for a cascading form — and what I found when one failed
I wrote the test. It passed. And it lied to me.
The form
Booking form: specialty → doctor → day → time. A classic cascading select. Each step unlocks the next.
The mental model writes itself: pick a specialty, the doctor dropdown enables. Pick a doctor, the day selector enables. Pick a day — and only then — the time slot selector enables.
I was testing the last step. Time select should be disabled until the user picks a day.
await page.selectOption('[data-testid="booking-specialty"]',
'Cardiology');
await page.selectOption('[data-testid="booking-doctor"]', { index: 0 });
await page.waitForSelector('[data-testid="booking-slot-day"]');
await expect(page.locator('[data-testid="booking-slot-time"]')
).toBeDisabled();
The test failed.
received: enabled
What I expected to find
Timing. A missing waitFor. The element hadn't fully loaded yet.
Added waitForLoadState. Added an explicit waitFor for the time element. Still: enabled.
Not a timing issue.
What was actually happening
Opened the page source. Two lines in the slot handler:
slotDayEl.value = dayKeys[0];
fillTimeOptionsForDay(dayKeys[0]);
When slots loaded, the page automatically selected the first available day — without any user input. Then it immediately filled the time options for that day.
The state "time select disabled before user picks a day" didn't exist in this product. By the time slots appeared, a day was already chosen.
This was an intentional UX decision — reduce clicks, pre-select the most sensible default.
The lie
I had previously written a different version of this test:
await expect(page.locator('[data-testid="booking-slot-time"]')
).toBeEnabled();
That test passed. I marked the behavior as verified.
What did it actually verify? That the time select was enabled after slots loaded.
What it didn't verify: whether the user had done anything to make it enabled.
The test confirmed the element's state. It said nothing about how the system got there.
If I had only written the toBeEnabled() version — if I'd never tried to test the disabled state — the CI would have stayed green, and I would have shipped a complete misunderstanding of how the form worked.
The failure taught me more than the passing test did.
The fix
The actual behavior: time select is disabled at page load, before any doctor is chosen. It enables when slots load — because the page auto-selects the first available day.
The correct test captures what actually happens:
// page load — no doctor selected yet, time is disabled
await expect(timeSelect).toBeDisabled();
// doctor selected, slots load, first day auto-selected — time enables
await page.selectOption(doctorSelect, { index: 0 });
await page.waitForSelector(slotDaySelect);
await expect(timeSelect).toBeEnabled();
This test documents what the system does — not what I assumed it would do.
Why it matters
A passing test doesn't tell you the system works the way you think it works.
It tells you the assertion succeeded given the actual system state — whatever produced that state.
The toBeEnabled() test confirmed a state. Not a behavior. The difference only became visible when I tried to test the opposite and the test failed.
Some of the most useful information about a system comes from tests that fail in unexpected ways.
The hidden assumption "I assumed a cascading form means each step requires explicit user selection. The product had a different model — reduce clicks by pre-selecting sensible defaults."
Part of the "Hidden Assumptions in Test Automation" series.
Full project (API + UI + E2E + CI + AI endpoint): GitHub
Top comments (2)
toBeEnabled() confirmed state, not behavior \u2014 I\u2019ve hit this trap too. We had a similar case: a test asserting a dropdown was enabled, green for weeks. Until the product team said \u201cthat dropdown shouldn\u2019t even be visible.\u201d Turns out the test was passing because of a side effect from the previous step, not because the actual feature was working.
Your line about \u201cthe test should document what the system actually does, not what I assumed it would do\u201d nails it. Are you planning more pieces in this \u201cfalse positive in test automation\u201d vein? This forensic style is rare and valuable.
Found a similar one recently — our "modal is visible" test was passing because the modal DOM was in the page, just hidden behind a backdrop with pointer-events:none. toBeVisible() was checking the wrong thing. These "passed but lied" tests are the sneakiest. Thanks for sharing this, validating UI states is harder than people think.