Selenium and Cypress literacy
You will inherit these. Enough to read an existing suite, keep it running, and make an honest case for or against migrating. Not somewhere to invest deeply if you are starting today.
Most testers do not choose a framework. They inherit one, on their second week, with four hundred tests and nobody left who wrote them.
So the useful skill is literacy rather than preference. Enough to read the suite, judge whether it is worth keeping, and fix what fails without rewriting anything.
Selenium WebDriver is the long-standing standard. It drives a real browser through a driver process, in almost any language, and it is a W3C specification rather than a product.
Cypress runs your test inside the browser alongside the application. That gives excellent developer experience and imposes real architectural limits.
Both are entirely capable of holding a good suite. Both have traps that show up in inherited code.
The terms you will hear
- WebDriver. The W3C protocol Selenium uses to control a browser.
- Explicit wait. Selenium's way of waiting for a condition. Correct, and verbose.
- Implicit wait. A global timeout applied to element lookups. A frequent source of confusion.
- Stale element reference. Selenium's error when the page re-rendered after you found an element.
- Command chain. Cypress's queued, asynchronous command list. Not promises, despite the syntax.
- Retry-ability. Cypress re-running an assertion until it passes or times out.
Selenium, and what goes wrong
Selenium is stable, universal and unopinionated. Nothing is provided for you, so every suite has a homemade layer around it, and the quality of the suite is the quality of that layer.
Three things dominate inherited Selenium code.
- Waits everywhere. Often a global implicit wait plus scattered explicit waits plus a few sleeps, all interacting unpredictably. Untangling this is usually the biggest single flake reduction available.
- Stale element errors. An element found before a re-render, used after. The fix is finding it again at the point of use rather than caching it.
- An unowned wrapper. A
BasePageclass with two hundred methods that nobody understands. Read it before changing anything, because behaviour hides in there.
Cypress, and what goes wrong
Cypress is pleasant to write and its constraints are architectural rather than incidental.
- The command chain is not promises.
cy.get()queues a command. Mixing it with async or await, or using its return value like a value, produces confusing failures. This is the most common misunderstanding in inherited Cypress. - Same-origin limits. Visiting two different origins in one test is constrained. Suites work around it, and the workaround is often what is failing.
- Retry-ability hides real races. An assertion that eventually passes looks fine. Under load, the product behaviour it tolerated becomes a defect.
- No true multi-tab. Tests that need two sessions get simulated, which is exactly the case that found a real double-spend defect in exploratory testing.
How to judge what you inherited
- Run it ten times on one commit. You now have a flake rate rather than an opinion, which is the flakiness method.
- Grep the assertions. Count the weak ones: truthy, defined, not-null, contains-on-a-number. This tells you whether the suite checks anything.
- Time one diagnosis. Pick a failure and time yourself understanding it. Over twenty minutes means the architecture is against you.
- Read the shared layer once, properly. An afternoon with the base classes and helpers saves weeks of surprises.
- Cull before anything else. Duplicates, dead features, cases that have never failed. Usually the fastest improvement available.
- Then decide. Keep and improve, keep and migrate the critical journeys only, or replace. Migration is a project with a real cost, so scope it deliberately.
A worked assessment
For example, here is one inherited Selenium suite, assessed in a day.
INHERITED 412 Selenium tests, Java, last touched 14 months ago
FLAKE RATE 10 runs on one commit
passed all 10 291
failed at least once 121 (29 percent)
causes: 74 timing (mixed implicit and explicit waits),
31 shared state (one seeded account),
16 stale element
ASSERTION STRENGTH grep across the suite
strong, specific value 188
weak (assertTrue on notNull) 147
no assertion at all 77 <-- these cannot fail
DIAGNOSIS TIME three failures timed
9 min, 24 min, 31 min. all three ended in the 900-line BasePage.
WHAT I DID, IN ORDER
1 deleted the 77 tests with no assertion. nobody objected.
2 deleted 96 duplicates and 41 tests for a removed feature.
3 removed the global implicit wait, fixed the 74 timing failures
with explicit conditions. flake rate fell to 4 percent.
4 gave each test its own account via a builder. flake rate 1 percent.
5 only then proposed migration, for the 30 critical journeys only.
AFTER
198 tests, 9 minutes, 1 percent flake rate, still Selenium.
migration of 30 journeys to Playwright approved on the evidence,
rather than on a preference.Notice that four of the five steps had nothing to do with the framework. The suite was not bad because it was Selenium. It was bad because nobody had culled it, and the waits were fighting each other.
How to show you know it
- A flake rate and an assertion count for a suite you inherited. It converts "the tests are unreliable" into a diagnosis.
- A cull before a migration. It shows you fix the cheap thing first.
- The command-chain explanation. Being able to explain why mixing await with Cypress commands misbehaves is a reliable literacy signal.
- A migration justified by numbers. Thirty journeys, on evidence, rather than a rewrite on taste.
Questions
Is Selenium obsolete?
No. It is a stable standard with the widest language and browser support, and plenty of good suites use it. It gives you less for free, so the quality depends more on your own layer.
Why do people complain about Cypress?
Mostly the architectural limits: origins, tabs, and the command chain being mistaken for promises. Within those limits it is pleasant and productive.
Should I rewrite an inherited suite?
Rarely, and not first. Cull, fix the waits, isolate the data, and measure again. If it is then still slow and flaky, migrate the journeys that matter and delete the rest.
What do I do about a 900-line base class?
Read it, write down what it actually does, and stop adding to it. Extract behaviour into fixtures and builders as you touch each area, which is the architecture path out.