Skip to content

Selenium Anti-Patterns

What was run

No new code — a catalog of real anti-patterns, each one tied directly to a genuine bug or surprising behavior this course actually hit and investigated earlier, rather than invented hypothetical examples.

1. Assuming .click() on a disabled element raises an exception

Level 1, Lesson 6 assumed .click() on disabled-btn would raise ElementNotInteractableException. The real run raised nothing at all. The anti-pattern: trusting an assumption about Selenium's behavior instead of the real, observed result — the fix there required adding a real onclick handler to the test app just to confirm the click genuinely did nothing.

2. Hardcoding time.sleep() instead of waiting for a real condition

The Level 1 capstone first hit a real NoSuchElementException from checking for a result immediately after a click. The quick fix (time.sleep(0.3)) was flagged honestly as a temporary bridge, not a real solution — this course's entire Level 2 exists because the real, durable fix is waiting for an observable condition, not guessing a duration that happens to work on one machine, once.

3. Trusting get_attribute() string values without checking their real format

Level 2, Lesson 4's exercise assumed the style attribute would contain the substring "display:none" — the real browser-normalized value was 'display: none;' (with a space). The anti-pattern: string-matching a browser-generated attribute value without first printing its real, exact repr().

4. Assuming a keyboard shortcut works identically across operating systems

Level 2, Lesson 8 sent Ctrl+A expecting a full text selection and got selectionStart == selectionEnd — no selection happened at all. The real OS convention on macOS is Cmd+A. The anti-pattern: assuming one platform's keyboard convention generalizes, instead of verifying the real selection state directly.

5. Assuming any local file path is uploadable via send_keys()

Level 2, Lesson 9 hit a real ERR_ACCESS_DENIED uploading a file from this course's own project directory — identical content uploaded fine from /tmp/. The anti-pattern: assuming Selenium's own successful send_keys() call means the browser process can actually read that path; a browser's own file-system sandboxing is a separate, real constraint.

6. Re-using a WebElement reference after the DOM changes

Level 2, Lesson 9 also demonstrated a real StaleElementReferenceException after a button's container was replaced — even though the new element had the identical id. The anti-pattern: treating a WebElement as a durable pointer to "the thing with this id" rather than what it actually is — a reference to one specific, now-destroyed DOM node.

7. Duplicating the same locator across every test file

Level 3, Lesson 1 measured the real cost directly: renaming one real HTML id crashed three separate duplicated-locator test functions simultaneously. The anti-pattern: skipping the Page Object Model because "it's just one extra class" — the real, measured cost of not having it is paying for every single call site when the application's markup changes once.

8. Defining a pytest_runtest_makereport hook inside a test module

Level 3, Lesson 10 wrote this hook directly inside a test file — it silently never fired at all, with zero screenshots captured despite a real failing test. Moving the identical function into conftest.py fixed it immediately. The anti-pattern: assuming a pytest hook works anywhere a function can be defined, rather than respecting pytest's real plugin-discovery rules.

9. Judging parallel speedup from a suite of very short tests

Level 4, Lesson 3 measured only an 11% real improvement from 4 parallel workers at 0.5s/test — not because parallelism doesn't work, but because fixed per-worker browser-launch overhead dominated. The anti-pattern: concluding "parallel testing doesn't help much here" from a short-test suite, instead of checking whether fixed overhead is the real bottleneck (as the same lesson's 2.0s/test re-run, at a real 1.67×, directly demonstrated).

How It Actually Works

Every entry above traces back to the same single root behavior: Selenium (and the real browser it drives) will do exactly what you told it to do, not what you assumed it would do, and it will not warn you when those two things diverge. .click() on a disabled element doesn't raise because nothing in the real DOM or WebDriver spec requires it to; get_attribute() returns the browser's own real, normalized string, not the one you expect; a stale WebElement reference fails specifically because it points at a real, now-gone DOM node, not at an abstract "element with this id." This course's own repeated methodology — run the real thing, look at the real result, investigate when it's surprising — is the direct antidote to every anti-pattern on this list, because each one was caught by exactly that process rather than by memorizing a list of rules in advance.

Common mistakes

  • Treating this list as exhaustive — it covers the real bugs this specific course hit; a different application under test will surface its own, different real surprises using the identical investigative method.
  • Memorizing the fixes without the underlying mechanism — e.g. remembering "use /tmp/ for uploads" without understanding why (browser process sandboxing), which won't transfer to a different real environment's specific sandboxing rules.
  • Assuming anti-patterns only matter at scale — several of these (the disabled-button assumption, the Ctrl+A assumption) were real, first-run surprises in a single test, not failures that only appear under load or scale.

Exercise

Pick any two anti-patterns from this list whose root cause you haven't directly reproduced yourself. Rerun the real underlying lesson's code locally, and before looking at its real output, write down your own prediction. Compare your prediction against the real result — this is the exact process that caught every one of these nine anti-patterns in the first place.