lecture 5 of 160 completed

Modern JavaScript apps and the elements that move under you

Single-page apps redraw constantly. Selenium sees the DOM as it is right now, which is why references go stale and clicks miss.

Selenium was designed when a page load meant a new page. Modern apps redraw pieces of themselves constantly without loading anything, and that mismatch is where most Selenium pain in a React or Angular app comes from.

Three specific problems, each with a specific fix.

The element you found no longer exists.

You already met StaleElementReferenceException in the earlier lecture. In a single-page app it happens far more often, because a small state change can replace a whole section of the DOM.

The fix is a habit: find late, use immediately.

// fragile: found early, used after a redraw
const row = await driver.findElement(By.css('[data-testid="contact-row"]'));
await driver.findElement(By.css('[data-testid="refresh"]')).click();
await row.click();                     // stale

// safer: find it after the change
await driver.findElement(By.css('[data-testid="refresh"]')).click();
await driver.wait(until.elementLocated(By.css('[data-testid="contact-row"]')), 5000);
await driver.findElement(By.css('[data-testid="contact-row"]')).click();

For lists that update while you work through them, re-find on each iteration rather than holding an array of elements from before.

The element moved while you were clicking it.

Animations and lazy-loading content shift the layout. Selenium calculates where to click, the element slides, and the click lands somewhere else. The error is often ElementClickInterceptedException, naming an element you were not aiming at.

Wait for the thing that settles, not for time. If a spinner is what causes the shift, wait for the spinner to disappear:

await driver.wait(until.stalenessOf(spinner), 5000);

The click landed on an overlay. A modal that is closing, a cookie banner, a toast. The message names the covering element, which is the clue people miss: read which element intercepted the click and deal with that one.

There is a tempting shortcut here, and it deserves a warning.

⚠️ A JavaScript click hides real bugs.

driver.executeScript('arguments[0].click()', el) clicks an element directly, ignoring whether it is visible, enabled or covered.

It makes the failure go away, and it also makes your test do something no user could do. If an invisible overlay blocks a button, a real customer is blocked too. Your test just stopped reporting it.

Use it only when you have understood the cause and decided the obstacle is a test artifact, never as a first response to a failing click.

Waiting for the app to be idle. Teams testing single-page apps often add a helper that waits for network activity to finish, using executeScript to check for pending requests. It works, and it is a workaround for something the newer tools do natively. Worth knowing that the difference exists, and that it is a legitimate reason a team might migrate.

Remember this: find late, use immediately, wait for the thing that settles, and never paper over a blocked click with a JavaScript click.

Reference: Waits.