A tool that can only see your marketing pages can only find marketing-page defects. The dashboard, the settings screen, the empty state a new account lands on, the form that takes money: all of it is behind a wall, and all of it is where the expensive things break.
Every product in this category has to solve that, and most solve it the same way: you give the tool an email and a password, it stores them, and on every run it drives your login form like a person would. That approach has three failure modes, and most companies already have at least one of them:
- It cannot get through modern sign-in. Google, Okta, 2FA, a magic link: none of them are a form with two fields, and none of them can be replayed from a stored password.
- It is a small piece of software that has to be maintained. A password plus a set of selectors for your login page keeps working only for as long as your login page does not change.
- It puts a real credential somewhere new. Your password now lives in a second system, and every future breach question includes that system.
The other way round
You sign in yourself, in your own browser, however your product actually works. Then you hand over what your browser is now holding:
labs session import auth.jsonThat file is a storage state: the cookies and local storage a signed-in browser is carrying. Exporting one takes a command from Playwright or a cookie-export extension. The sign-in happened where you were, through whatever your identity provider wanted, and nothing on this side ever saw a password or a login form. There is no recipe to maintain, because there is no recipe.
Key features of session import
1. It opens the part of the product that matters
Here is the same product visited twice, once with a session and once without. It is a fixture we wrote and broke on purpose, so the routes are known and the sign-in is real.
scripts/sessionfigs.cjs, which signs in to
fixtures/notely.cjs in a real browser, exports the storage state, and visits every
route twice.- Two of the eight bounce to the login route signed out, which any crawler would notice.
- Two more answer 200 OK and serve a sign-in form. A run that judges by status code records those as healthy pages and reports on a screen it never saw.
- One is genuinely missing on both sides, which is a real defect rather than a wall, and the figure keeps it visible instead of rounding the coverage up.
2. It never prints what it is holding
A storage state is a bearer credential. Anything that echoes one into a terminal, a log or a report has handed it to whoever reads that. So the import summarises and never quotes:
imported: 2 cookie(s) for [the host, elided] · 0 origin(s) with local storage
Real output from describeImport() in lib/login.cjs,
run against a session exported from the fixture during this build.
- Counts and domains, and nothing else. No cookie value is ever rendered.
- The file is written owner-only into the account’s own state directory, and the
permissions are set twice, because
writeFileSynconly applies its mode when it creates a file. An import over a previous one would otherwise inherit that one’s permissions. - It fails closed. Anything that is not a storage state is refused with the reason rather than stored and discovered later, and the stored file is re-validated on every read, because it is a file on disk that a person can edit.
3. It checks the session is alive before it trusts it
- Sessions expire. That is the one real cost of this approach against a stored password, and it is worth being straight about: a password recipe can sign itself in again at three in the morning, and an imported session cannot.
- What the run will not do is quietly carry on. Before anything uses an imported session it is checked against the live product: open a context wearing it, go where a signed-in user goes, and read the answer.
- If the product returns its sign-in form or bounces to the login route, the session is dead and the run says so. The alternative is a run that roams the signed-out site for twenty minutes and reports that everything is fine, which is the same lie as the 200 above.
Use an account you can throw away
This matters more than it sounds. A run drives a real browser as a real signed-in user, and it will press things. It is supposed to press things.
Give it a test account on a staging environment. Not your own login, and never an account with billing or admin rights. The honest framing is that you are handing a tester the keys and asking it to try the doors, and the way that stays safe is the account being worth nothing rather than the tester being careful.
Getting started with session import
- Make a throwaway account on staging, with no billing and no admin rights.
- Sign in as that account in your own browser, through whatever your identity provider actually asks for.
- Export the storage state to a file, with Playwright’s
storageState()or a cookie-export extension. - Import it with
labs session import auth.json. The summary tells you what landed, in counts and domains. - Run an audit. The session is checked for liveness first, and every screen behind the wall is now in scope.
When it expires, repeat step two. That is the whole maintenance story, and it is a minute of your time rather than a login recipe that breaks silently when the sign-in page is redesigned.