The three runtime failures web apps testing has to cover

Leftover state

  • A modal closes and leaves a scroll lock. A filter persists where it makes no sense.

  • Only shows on the second interaction, so a fresh reproduction never finds it.

Render before data

  • The page paints, the fetch resolves, nothing re-renders.

  • Usually intermittent, which is why it survives review.

The second user

  • Ownership and visibility assumptions built from the developer's own session.

  • Works perfectly for everyone who built it.

The check that catches most of it

Do the action twice, as two different users, with a reload in between. That single pattern hits all three categories, and it is the thing manual testing skips because the first attempt worked.

What to assert

  • Something a reload would confirm. A success message is not evidence that anything persisted. Fetch the thing back.

  • The absence of what should not be there. After deleting, the row is gone. After signing out, the data is not visible.

  • Behavior for someone else. The second account cannot see the first account's records.

Where local development fits

Web app work happens on localhost long before anything deploys, and a check that only works against a public URL arrives too late to change what you build. The CLI can tunnel to an app running on your own machine for frontend runs, so the check is available while you are still working. Details are in the CLI repository.

Terminal

npm install -g @testsprite/testsprite-cli
testsprite setup

The dashboard covers the same ground for anyone who does not want a local install.

The two-tab test

There is one manual check worth doing before any release that finds more than its cost, and almost nobody does it: open the app in two tabs.

Do something in the first, then look at the second. Most web apps behave badly here in some way, and the badness is informative. A list that does not update is usually fine. A stale action that succeeds and overwrites newer data is a real bug. A session that logs out one tab when the other refreshes a token is a real bug. An optimistic update that never reconciles is a real bug.

It takes two minutes, it needs no tooling, and it exercises the exact seam where server state, browser state and user expectation disagree. If you only add one habit from this page, add that one.

Make it continuous

Runtime failures are exactly the ones that reappear, because the conditions that produce them are easy to recreate accidentally.

  • The GitHub App is a webhook you set up in the TestSprite dashboard. It listens for the deployment event your pipeline already produces, so nothing in your repository changes.

  • GitHub Actions puts the step inside your own workflow, configured from the terminal.

How TestSprite exercises the running app

It opens the deployed application and uses it the way a person would, which is the only way to see leftover state, render-before-data and second-user behavior. Cases are described in plain language and generated from your product, and they assert on things a reload would confirm rather than on a message the client rendered.

The second-user check is a first-class case rather than a setup exercise, which matters because permission assumptions are the failure most likely to reach a customer and least likely to be noticed by whoever built the feature.

The checks then keep running on every change, so the three-state disagreements that produced a bug once cannot reappear quietly after the branch is merged.

Why do these not show up in code review?

Because they are properties of an interaction between server state, browser state and history. A diff shows none of those.

Can we test this with unit tests?

Partly, with a lot of mocking, and the mocks encode the same assumptions the code does. Running the real thing is what breaks the tie.

How do we test the second user reliably?

Keep a second account per environment and run the same flow as both. It is the cheapest high-yield check in web app testing.

What about mobile browsers?

Viewport differences surface a separate set of layout and interaction problems. Worth covering the main flows at a mobile size if you have mobile traffic.

How much of this can run locally?

Frontend runs can reach an app on your machine through a tunnel. Backend targets that are not reachable still need an exposed environment.

The short version

Do it twice, as two users, with a reload.

Web apps testing has to cover leftover state, render-before-data and second-user behavior, none of which are visible in source. Assert what a reload confirms, check the absence of what should be gone, and run the same flow as someone else.