To test session timeout and auto-logout flows with AI, you drive the app to the idle limit, assert the expected outcome (a redirect to login, a session-expired message, or a countdown warning modal), and then confirm that re-authentication works and lands the user back where they belong. The honest catch is that you cannot fast-forward a server clock from the browser, so the practical play is to point your tests at a test environment configured with a short timeout (say 60 to 120 seconds instead of 30 minutes), or to use whatever short-lived token your staging tier exposes. With BrowserBash, a free and open-source natural-language browser automation CLI from The Testing Academy, you describe each of those checks as plain-English steps in a Markdown test file, and an AI agent verifies the behavior against the live page rather than against brittle selectors.
This guide covers the three parts of a complete timeout test, the honest limits around waiting, and the CI wiring you need to run these checks unattended. Install once and you are ready:
npm install -g browserbash-cli
What a Complete Session-Timeout Test Actually Checks
A real session-timeout flow has more moving parts than "wait, then see the login page." A thorough suite covers four distinct behaviors, and they often live in separate tests because they have different timing needs.
- Idle timeout and redirect. After a period of no interaction, the server invalidates the session and the next action (or a background poll) bounces the user to the login screen or shows a session-expired notice.
- The warning modal. Many apps warn before they log you out: a "You will be signed out in 60 seconds" dialog with "Stay signed in" and "Log out" buttons. You need to assert that it appears, that clicking "Stay signed in" extends the session, and that ignoring it proceeds to logout.
- Protected-route enforcement. Once the session is dead, navigating directly to a protected URL must not show stale content. It should redirect to login, ideally preserving the intended destination so the user returns there after re-auth.
- Clean re-authentication. After logout, signing in again should work normally and, where applicable, restore the deep link the user was trying to reach.
BrowserBash finds elements through the accessibility tree (roles, accessible names, and states) plus the DOM, not CSS classes, so a modal titled "Session expiring" is matched by what a user would read, not by a class hash that changes between deploys. That matters here because session and auth UI is exactly the kind of surface that gets restyled often. For a deeper look at how the agent locates UI, see the features page.
The Core Problem: You Cannot Fast-Forward the Clock
Let us be blunt about the hard part. Browser automation runs in a real browser against a real server. The session lifetime is enforced server-side, so from the client you have exactly two honest options:
- Wait it out. If the timeout is 30 minutes, a faithful end-to-end test takes 30 minutes plus assertions. That is real, and there is no browser trick that legitimately shortcuts it.
- Shorten the timeout in a test environment. Configure staging (or a dedicated test config) with a short idle limit, for example 60 to 120 seconds. This is the approach almost every team should use. You are testing the same logout machinery, just with a parameter set low enough to run in CI.
A third, partial option is to manipulate the session from outside the browser between steps: expire the token via an admin API, delete the session cookie, or revoke server-side. That verifies the "what happens when the session is already dead" half of the story (redirect, protected-route enforcement, re-auth) without waiting, but it does not exercise the client-side idle timer or the warning modal. Use it to complement, not replace, a genuine idle test.
BrowserBash uses Playwright's built-in auto-wait with a 15-second ceiling for late-rendering elements, so you never write manual sleeps for normal UI. But that 15-second ceiling is about waiting for an element to appear, not about waiting out a multi-minute server timeout. For the timeout itself you need a real wait, which is why a short test-env timeout is the difference between a 90-second test and a 30-minute one.
Writing the Idle-Timeout Test
Tests in BrowserBash are intent, not selectors. You write a Markdown *_test.md file with a title, numbered or bulleted steps, optional {{variables}} for credentials, and @import to reuse other tests. Here is a self-contained idle-timeout test that assumes a test environment with a short (roughly 90-second) idle limit.
# Idle session timeout redirects to login
1. Go to https://staging.example.com/dashboard
2. Confirm the dashboard heading "Welcome back" is visible
3. Do not interact with the page for 95 seconds
4. Reload the page or click any navigation link
5. Confirm the page redirects to the login screen
6. Confirm a message like "Your session has expired" or "Please sign in again" is visible
Run it directly:
browserbash testmd run ./idle_timeout_test.md
A few notes on why this is written the way it is. Step 3 is the deliberate idle wait that matches your test-env timeout, with a small buffer (95 seconds for a 90-second limit) so you are safely past the threshold. Step 4 is important because many apps do not log you out until the next request: the session may already be dead server-side, but the browser still shows the old page until you act. Asserting on the visible expiry message in step 6 is what makes this a behavior test rather than a URL check, and the agent matches that text the way a user would read it.
To avoid duplicating your login flow in every test, compose it with @import. If you already have a reusable login test, your dashboard setup becomes one line. See reusing a login session across browser tests for the full pattern, including how to keep one authenticated session warm across multiple checks.
# Idle timeout from an authenticated session
@import ./login_test.md
1. Confirm the dashboard heading "Welcome back" is visible
2. Do not interact with the page for 95 seconds
3. Reload the page
4. Confirm the login screen is shown with a session-expired notice
Asserting the Warning Modal and "Stay Signed In"
The countdown warning is its own behavior and deserves its own test. The trick is to wait long enough for the warning to fire but not so long that the session fully expires, then verify both branches: extending and not extending. The modal and toast handling here follows the same approach described in testing modals, toasts, and notifications with AI.
# Session-expiry warning modal extends the session
@import ./login_test.md
1. Confirm the dashboard heading "Welcome back" is visible
2. Do not interact with the page for 75 seconds
3. Confirm a dialog appears warning that the session is about to expire
4. Confirm the dialog has a "Stay signed in" button and a "Log out" button
5. Click "Stay signed in"
6. Confirm the warning dialog closes
7. Confirm the dashboard is still usable by clicking the "Reports" link
8. Confirm the Reports page loads without redirecting to login
For the negative branch, where the user ignores the warning and gets logged out, write a sibling test that waits past the countdown instead of clicking:
# Ignoring the expiry warning logs the user out
@import ./login_test.md
1. Confirm the dashboard heading "Welcome back" is visible
2. Do not interact with the page for 95 seconds
3. Confirm the session-expiry warning appeared and then the user is signed out
4. Confirm the login screen is visible
Because the agent re-derives what is on screen from the live state on every action, the warning modal does not need a stored selector. If your team ships the modal inside an iframe or a Shadow DOM web component, BrowserBash handles both, so a design-system dialog that wraps its content in Shadow DOM still gets matched by its accessible name.
Protected Routes and Clean Re-Authentication
After the session is dead, two things must hold: a protected URL must not leak stale content, and signing back in must work. Here is a test that pins both, using an out-of-band expiry to skip the wait where you only care about the post-expiry behavior.
# Expired session blocks protected routes and allows re-login
@import ./login_test.md
1. Confirm the dashboard heading "Welcome back" is visible
2. Open a new context where the session cookie has been cleared
3. Go directly to https://staging.example.com/account/billing
4. Confirm the page does NOT show billing details
5. Confirm the page redirects to the login screen
6. Sign in with username {{USERNAME}} and password {{PASSWORD}}
7. Confirm the user lands on the billing page that was originally requested
The {{USERNAME}} and {{PASSWORD}} placeholders are masked as ***** in all logs and output, so credentials never appear in your terminal history or CI logs. Store the real values in environment variables or your CI secret store and pass them in. The full mechanics, including precedence and masking behavior, are covered in the variables and secrets tutorial.
Step 4 is the security-critical assertion. A surprising number of apps render the protected page first and redirect a half-second later, which means a real user can glimpse billing data before the bounce. Asserting the absence of sensitive content, not just the eventual URL, is what catches that class of bug.
Wiring Timeout Tests Into CI
These tests run unattended in CI like any other BrowserBash run. The --agent flag emits NDJSON so a pipeline can parse structured progress, --headless runs without a display, and --record captures a webm video plus screenshots for the rare genuine failure. Exit codes are the contract your pipeline keys on: 0 pass, 1 fail, 2 error, 3 timeout. A Result.md is written per run for humans to read.
# .github/workflows/session-timeout.yml
name: Session timeout checks
on:
schedule:
- cron: "0 6 * * *" # nightly, since these tests include real waits
workflow_dispatch: {}
jobs:
timeout-suite:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm install -g browserbash-cli
- name: Run timeout suite
env:
USERNAME: ${{ secrets.STAGING_USERNAME }}
PASSWORD: ${{ secrets.STAGING_PASSWORD }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
browserbash testmd run ./idle_timeout_test.md \
--agent --headless --record
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: timeout-run
path: |
Result.md
*.webm
recordings/
A note on scheduling: because faithful idle tests include real waits, run the timeout suite on a nightly schedule (or a separate, less frequent job) rather than blocking every pull request. Your fast PR gate can use the out-of-band-expiry tests, which assert redirect and re-auth behavior without the idle wait, while the nightly job runs the genuine idle-timer and warning-modal tests against the short-timeout test environment.
BrowserBash emits the signal; you wire the integration. It does not natively post to Slack or open Jira tickets. To alert on a failure, read the exit code in your pipeline step and call your own notifier:
browserbash testmd run ./idle_timeout_test.md --agent --headless --record
code=$?
if [ "$code" -ne 0 ]; then
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
--data "{\"text\":\"Session-timeout suite failed (exit $code). See Result.md and the recording.\"}"
exit "$code"
fi
The NDJSON stream from --agent is also parseable if you want richer reporting: pipe it to a small script that extracts step outcomes and attaches them to your dashboard. You can also opt in to the hosted dashboard with --upload (free runs kept 15 days) or run a local one with browserbash dashboard.
Choosing a Model for Long Waited Flows
The default model resolution is auto: it resolves Ollama first, then ANTHROPIC_API_KEY, then OPENROUTER_API_KEY (free models exist on OpenRouter). Running locally with Ollama means nothing leaves your machine, which is attractive for auth and session flows that touch credentials.
One honest caveat specific to this topic: small local models (8B and under) get flaky on long, multi-step flows, and a timeout test is exactly that. It is a long sequence with a big idle gap in the middle, several conditional branches (warning shown or not, extend or expire), and assertions on subtle text. For these flows, prefer a 70B-class local model (Qwen3 or Llama 3.3) or a hosted model. If you are running offline-first, keep the smaller model for short smoke checks and reserve the larger model for the timeout and re-auth suite. For more on this trade-off and how to keep runs deterministic, the learn hub and the guide on reducing flaky end-to-end tests are good next reads.
Honest Limits
Session-timeout testing is one of the areas where browser automation, AI or otherwise, hits genuine walls. Here is where BrowserBash will not save you from physics or product decisions.
- No clock control. The single biggest limit. You cannot fast-forward the server's session clock from the browser. If your production timeout is 30 minutes and you refuse to shorten it in any environment, a faithful idle test will take 30 minutes. There is no honest shortcut. The fix is a config knob in staging, which is a product and infra decision, not a tool feature.
- Out-of-band expiry needs your help. Clearing a cookie or revoking a token server-side requires either a step the agent can perform in the browser (open a fresh context, clear storage) or an API call you script alongside the run. BrowserBash drives the browser; it does not manage your auth backend for you.
- Sliding vs absolute timeouts are easy to mis-test. If your app uses sliding expiration (every request resets the idle timer), any background poll, heartbeat, or analytics ping can silently keep the session alive and your "idle" test never times out. Diagnosing that usually means inspecting network behavior, which is outside a pure behavior assertion. If a timeout test never fires, suspect a heartbeat before you suspect the test.
- Timing-sensitive warnings are inherently racy. A test that must wait 75 seconds to catch a modal that lives for 60 seconds has a narrow window. Build in buffers, and accept that very tight countdowns may need the timeout widened slightly in the test environment to be reliably observable.
- Small models drift on long flows. As noted above, sub-8B local models lose the thread on these long, branchy sequences. This is a real reliability ceiling, not a tuning detail. Use a larger model for this suite.
None of these are reasons to skip timeout testing. They are reasons to design for it: a short-timeout test environment, an out-of-band expiry helper for the fast path, a nightly schedule for the faithful idle runs, and a capable model. With those in place, BrowserBash turns a tedious, manual, easy-to-forget check into a repeatable behavior assertion.
FAQ
How do I test a session timeout without waiting 30 minutes?
Configure a test environment with a short idle limit, for example 60 to 120 seconds, and point your BrowserBash test at it. You are exercising the exact same logout machinery, just with a parameter low enough to finish in CI. For the post-expiry behavior alone (redirect, protected-route enforcement, re-auth) you can skip the wait entirely by clearing the session cookie or revoking the token out of band, then asserting the app behaves as expected. Reserve the genuine, full-length idle wait for a nightly job.
Can BrowserBash assert that the warning modal appears before logout?
Yes. Write a step like "Confirm a dialog appears warning that the session is about to expire" and the agent matches it by its accessible name and role through the accessibility tree, not by a CSS selector. You can then assert both branches in separate tests: clicking "Stay signed in" keeps the session alive, while ignoring the countdown proceeds to logout. Modals inside iframes or Shadow DOM are handled too, so a design-system dialog still gets matched.
How do I make sure an expired session does not leak protected content?
Add an explicit negative assertion before the redirect check. A step like "Confirm the page does NOT show billing details" catches apps that render the protected page first and bounce a half-second later, which is the window where a real user can glimpse sensitive data. Pair it with "Confirm the page redirects to the login screen" so you verify both the absence of stale content and the eventual redirect.
How do I get a Slack or Jira alert when a timeout test fails?
BrowserBash emits the signal and you wire the integration; it does not post to Slack or Jira natively. Read the process exit code after the run (0 pass, 1 fail, 2 error, 3 timeout) and, on a non-zero code, call your own webhook or ticket API from the same CI step. The --agent NDJSON stream, the per-run Result.md, and --record artifacts (webm plus screenshots) give you the detail to attach to the alert.
Get Started Free
BrowserBash is free and open-source under Apache-2.0, with no paid tier between you and a reliable timeout suite. Install it with npm install -g browserbash-cli, configure a short idle limit in your test environment, and write your first idle-timeout test as a plain-English Markdown file. Then layer in the warning-modal, protected-route, and re-auth checks, schedule the faithful runs nightly, and let your fast PR gate cover the out-of-band path. Explore the full toolkit on the features page and start testing session timeout and auto-logout flows with AI today.