Skip to Content

Shared State

Glubean provides two scopes for sharing runtime state between raw tests: module scope (within a file) and session scope (across files).

Use these deliberately. For durable business lifecycles, prefer workflow(). For imperative multi-step checks, prefer the test() builder with .setup(), .step(), and .teardown(). Shared state is the escape hatch when separate exported tests need to cooperate.

Session scope is per run. It exists only for a single glubean run execution and is not persisted across runs.

Module scope — within a file

Tests in the same file run sequentially in a single process. Module-level variables persist between tests:

import { configure, test } from "@glubean/sdk"; const { http: api } = configure({ http: { prefixUrl: "{{BASE_URL}}" }, }); let authToken: string; export const login = test("login", async (ctx) => { const res = await api.post("auth/login", { json: { user: ctx.vars.require("USER"), pass: ctx.secrets.require("PASS") }, }); const body = await res.json<{ access_token: string }>(); authToken = body.access_token; ctx.assert(!!authToken, "Login returns an access token"); }); export const getProfile = test("get-profile", async (ctx) => { // authToken is available — same process, same module const res = await api.get("me", { headers: { Authorization: `Bearer ${authToken}` }, }); ctx.expect(res).toHaveStatus(200); });

Use module scope when:

  • Tests in the same file share setup (login, seed data, etc.)
  • You want ordered execution with shared context
  • The shared value doesn’t need to cross file boundaries

Avoid module scope when a single test builder or workflow() can express the same lifecycle. Running one exported test directly skips earlier exports, so module variables may not be initialized.

Session scope — across files

When you need to share state across multiple test files (e.g., authenticate once, use the token everywhere), use session scope.

1. Create session.ts

Place a session.ts file at your test root. The runner auto-discovers it:

// session.ts import { configure, defineSession } from "@glubean/sdk"; const { http: api } = configure({ http: { prefixUrl: "{{BASE_URL}}" }, }); export default defineSession({ async setup(ctx) { const res = await api.post("auth/login", { json: { user: ctx.vars.require("TEST_USER"), pass: ctx.secrets.require("TEST_PASS"), }, }); const { access_token, user_id } = await res.json<{ access_token: string; user_id: string; }>(); ctx.session.set("token", access_token); ctx.session.set("userId", user_id); ctx.log("Authenticated as user " + user_id); }, async teardown(ctx) { const token = ctx.session.get("token"); if (token) { await api.post("auth/logout", { headers: { Authorization: `Bearer ${token}` }, }); } }, });

2. Read session values in tests

Any test file can read session values via ctx.session:

// tests/users.test.ts import { configure, test } from "@glubean/sdk"; const { http: api } = configure({ http: { prefixUrl: "{{BASE_URL}}" }, }); export const listUsers = test("list-users", async (ctx) => { const token = ctx.session.require("token"); // throws if missing const res = await api.get("users", { headers: { Authorization: `Bearer ${token}` }, }); ctx.expect(res).toHaveStatus(200); });

3. Session lifecycle

glubean run tests/ | +- 1. Discover session.ts | +- 2. Run setup() | Sets token, userId | +- 3. Run test files (sequential) | users.test.ts -> reads token | orders.test.ts -> reads token, userId; writes orderId | billing.test.ts -> reads orderId | +- 4. Run teardown() | Logs out | Done

Session API

MethodReturnsBehavior
ctx.session.get(key)T | undefinedReturns value if present
ctx.session.require(key)TReturns value or throws
ctx.session.set(key, value)voidSets JSON-serializable value
ctx.session.entries()Record<string, unknown>Returns all key-value pairs

File discovery

The runner looks for session.ts or session.setup.ts starting from your test directory and walking up toward the project root. Running a single file still walks up to find the nearest session.ts.

Use --no-session to skip session setup/teardown:

glubean run tests/users.test.ts --no-session

When to use which

NeedUse
Share state within one fileModule-level let variables
Share state across filessession.ts + ctx.session
Share config (baseUrl, headers)configure() in a shared config file
Share secrets.env.secrets + ctx.secrets
Project a lifecycleworkflow()
Keep one imperative lifecycle togethertest() builder

Current limitations

  • Sequential mode only. Session state propagates file-to-file in discovery order.
  • JSON-serializable values only. Strings, numbers, booleans, arrays, plain objects.
  • No parallel guarantees. Cross-file session writes require sequential execution.
Last updated on