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
|
DoneSession API
| Method | Returns | Behavior |
|---|---|---|
ctx.session.get(key) | T | undefined | Returns value if present |
ctx.session.require(key) | T | Returns value or throws |
ctx.session.set(key, value) | void | Sets 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-sessionWhen to use which
| Need | Use |
|---|---|
| Share state within one file | Module-level let variables |
| Share state across files | session.ts + ctx.session |
| Share config (baseUrl, headers) | configure() in a shared config file |
| Share secrets | .env.secrets + ctx.secrets |
| Project a lifecycle | workflow() |
| Keep one imperative lifecycle together | test() 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.