Create and edit *.probitas.ts files. Use when writing new Probitas scenarios, editing existing ones, or implementing E2E tests.
Write Probitas E2E test scenarios using the scenario API. Create multi-step tests with HTTP, database, and other clients, then validate results using expect() assertions.
/plugin marketplace add probitas-test/claude-plugins/plugin install probitas@probitas-testsonnetThis agent operates in English. All prompts should be in English for optimal performance.
import { client, expect, scenario } from "jsr:@probitas/probitas";
import { faker } from "jsr:@probitas/probitas"; // if needed
export default scenario("Test name", { tags: ["..."] })
.resource("http", () =>
client.http.createHttpClient({
url: Deno.env.get("API_URL") ?? "http://localhost:8080",
}))
.step("Step name", async (ctx) => {
const res = await ctx.resources.http.post("/endpoint", { body: {} });
expect(res).toBeOk().toHaveStatus(200);
return res.json as { id: number } | null;
})
.build();
/probitas-check, fix lint/type issuesimport { client, expect, scenario, Skip } from "jsr:@probitas/probitas";
import { faker, FakeTime, spy, stub } from "jsr:@probitas/probitas";
CRITICAL: Always use the correct factory function from client.* namespace.
| Client | Factory Function | Use Case |
|---|---|---|
| HTTP | client.http.createHttpClient() | REST APIs, webhooks |
| HTTP OIDC | client.http.oidc.createOidcHttpClient() | OAuth 2.0/OIDC APIs |
| PostgreSQL | client.sql.postgres.createPostgresClient() | PostgreSQL databases |
| MySQL | client.sql.mysql.createMySqlClient() | MySQL databases |
| SQLite | client.sql.sqlite.createSqliteClient() | Embedded databases |
| DuckDB | client.sql.duckdb.createDuckDbClient() | Analytics databases |
| gRPC | client.grpc.createGrpcClient() | gRPC services |
| ConnectRPC | client.connectrpc.createConnectRpcClient() | Connect/gRPC-Web |
| GraphQL | client.graphql.createGraphqlClient() | GraphQL APIs |
| Redis | client.redis.createRedisClient() | Cache, pub/sub |
| MongoDB | client.mongodb.createMongoClient() | Document databases |
| Deno KV | client.deno_kv.createDenoKvClient() | Deno KV store |
| RabbitMQ | client.rabbitmq.createRabbitMqClient() | AMQP message queues |
| SQS | client.sqs.createSqsClient() | AWS message queues |
Examples:
// HTTP
.resource("http", () =>
client.http.createHttpClient({
url: Deno.env.get("API_URL") ?? "http://localhost:8080",
}))
// HTTP with OIDC authentication
.resource("http", () =>
client.http.oidc.createOidcHttpClient({
url: Deno.env.get("API_URL") ?? "http://localhost:8080",
oidc: {
issuer: Deno.env.get("OIDC_ISSUER") ?? "http://localhost:8080",
clientId: "test-client",
},
}))
// PostgreSQL
.resource("db", () =>
client.sql.postgres.createPostgresClient({
url: Deno.env.get("DATABASE_URL") ?? "postgres://user:pass@localhost:5432/db",
}))
// GraphQL
.resource("graphql", () =>
client.graphql.createGraphqlClient({
url: Deno.env.get("GRAPHQL_URL") ?? "http://localhost:4000/graphql",
}))
Follow the same pattern for other clients using the factory functions from the table above.
Each scenario = steps that DEPEND on each other via ctx.previous.
Before adding a step, ask: "Does this step need ctx.previous?"
// ❌ BAD - Independent tests in one scenario
scenario("Tests")
.step("Test A", async (ctx) => { /* no ctx.previous */ })
.step("Test B", async (ctx) => { /* no ctx.previous */ })
.build();
// ✅ GOOD - Separate scenarios for independent tests
const http = () => client.http.createHttpClient({ url });
export default [
scenario("Test A").resource("http", http).step(...).build(),
scenario("Test B").resource("http", http).step(...).build(),
];
// ✅ GOOD - Multi-step scenario with dependencies
scenario("User CRUD")
.step("Create", async (ctx) => { return { id: 1 }; })
.step("Update", async (ctx) => { const { id } = ctx.previous!; ... })
.build();
export default scenario(...).build()export default [scenario(...).build(), ...].build()Available in .resource(), .setup(), .step():
ctx.previous - Previous step's return valuectx.results - All previous step results (tuple)ctx.resources - Registered resources by namectx.store - Shared Map across stepsctx.signal - AbortSignal for cancelable operationsNEVER use if/throw or manual checks. ALWAYS use expect() assertions.
MANDATORY PROCESS - BEFORE writing ANY assertion:
Identify the response type and corresponding Expectation interface:
HttpResponseExpectationGraphqlResponseExpectationGrpcResponseExpectationConnectRpcResponseExpectationSqlQueryResultExpectationRedisExpectation (or RedisGetResultExpectation, RedisSetResultExpectation, etc.)MongoExpectation (or MongoFindResultExpectation, MongoInsertOneResultExpectation, etc.)RabbitMqExpectation (or RabbitMqPublishResultExpectation, RabbitMqConsumeResultExpectation, etc.)SqsExpectation (or SqsSendResultExpectation, SqsReceiveResultExpectation, etc.)DenoKvExpectation (or DenoKvGetResultExpectation, DenoKvSetResultExpectation, etc.)Use /probitas:probitas-expect-methods command to get ACTUAL available methods for that specific interface:
# Example for HTTP:
/probitas:probitas-expect-methods HttpResponseExpectation
# Example for SQL:
/probitas:probitas-expect-methods SqlQueryResultExpectation
# Example for MongoDB find:
/probitas:probitas-expect-methods MongoFindResultExpectation
# Example for RabbitMQ publish:
/probitas:probitas-expect-methods RabbitMqPublishResultExpectation
toBeOk, toHaveStatus, etc.Choose the appropriate expect method based on the actual API - DO NOT improvise with if/throw
Why using /probitas:probitas-expect-methods is mandatory:
MongoFindResultExpectation vs MongoInsertOneResultExpectation)// ❌ FORBIDDEN - if/throw for status check
if (res.statusCode !== 0) {
throw new Error(`Expected status 0, got ${res.statusCode}`);
}
// ❌ FORBIDDEN - JSON.stringify check
const data = res.data<{ Metadata?: Record<string, unknown> }>()!;
if (JSON.stringify(data.Metadata).includes("token")) {
throw new Error("Token found");
}
// ❌ FORBIDDEN - manual property check
if (data.Metadata && "x-internal-token" in data.Metadata) {
throw new Error("Token found");
}
// ✅ CORRECT - use expect() assertions ONLY
expect(res)
.toBeOk()
.toHaveDataMatching({ statusCode: 0 })
.not.toHaveDataProperty(["Metadata", "x-internal-token"]);
Chain ALL assertions in ONE expect() call:
// ✅ CORRECT - single chain, data extraction at end
expect(res)
.toBeOk()
.toHaveDataMatching({ statusCode: 0 })
.toHaveDataProperty("metadata");
return res.data as { metadata?: Record<string, unknown> } | null;
For nested property paths, use dot-notation strings:
// ✅ CORRECT - dot-notation for nested paths
expect(res)
.toBeOk()
.toHaveDataProperty("Metadata")
.not.toHaveDataProperty("Metadata.x-internal-token");
Use array syntax only when property names contain dots:
// ✅ CORRECT - array syntax when property name contains "."
expect(res)
.toBeOk()
.not.toHaveDataProperty(["api.internal", "token"]); // "api.internal" is the actual property name
// ❌ WRONG - split expect calls
expect(res).toHaveDataMatching({ statusCode: 0 });
const data = res.data as ...; // Don't extract between expects!
expect(res).toHaveDataProperty("metadata");
// ❌ WRONG - separate expect for .not
expect(res).toHaveDataProperty("metadata");
expect(res).not.toHaveDataProperty("Metadata.x-internal-token"); // Chain it!
DO NOT GUESS API PATTERNS:
// ❌ Missing export default / .build()
const s = scenario("Test").step(() => {});
// ✅ export default scenario("Test").step(() => {}).build();
// ❌ Hardcoded URLs
client.http.createHttpClient({ url: "http://localhost:8080" });
// ✅ Use env vars with fallback
client.http.createHttpClient({
url: Deno.env.get("API_URL") ?? "http://localhost:8080",
});
// ❌ json is a property, not a method
const data = res.json<T>()!;
// ✅ Type assertion on property
const data = res.json as T | null;
export default + .build()Deno.env.get("API_URL"))/probitas:probitas-expect-methods to find correct expect methodsexpect() - NO if/throw*.probitas.tsDesigns feature architectures by analyzing existing codebase patterns and conventions, then providing comprehensive implementation blueprints with specific files to create/modify, component designs, data flows, and build sequences