Referencia
SDK cliente
Referencia del SDK cliente, recursos de prueba, comparadores, informes con datos sensibles ocultos, fallos SMTP y mensajes capturados.Importa el cliente de pruebas desde inboxtap/client. Se comunica con la API HTTP local y crea
direcciones de destinatario únicas en el proceso de pruebas.
Crear un cliente
import { InboxTapClient } from "inboxtap/client";
const inboxTap = new InboxTapClient({
baseUrl: "http://localhost:8025",
});baseUrl tiene como valor predeterminado http://localhost:8025. Un valor domain opcional evita
la petición inicial de estado cuando la prueba ya conoce el dominio de destinatarios configurado.
Crear un buzón
const inbox = await inboxTap.createInbox({ alias: "password-reset" });
console.log(inbox.address);El alias se normaliza a letras minúsculas, dígitos y guiones, y después se añade un sufijo
aleatorio de 12 caracteres. El alias por defecto es test.
Puntos de entrada para recursos de prueba
Los recursos de los ejecutores de pruebas se publican mediante rutas secundarias aisladas, para que el servidor raíz y el SDK cliente no carguen dependencias de prueba opcionales.
| Importación | Exportación principal | Ciclo de vida |
|---|---|---|
inboxtap/fixtures | startInboxTapFixture() | Inicio explícito y close() idempotente |
inboxtap/fixtures/bun | setupInboxTap() | Preparación y limpieza del archivo mediante las funciones del ciclo de vida de Bun |
inboxtap/fixtures/vitest | extendInboxTap() | Servidor por archivo, buzón por prueba |
inboxtap/fixtures/playwright | extendInboxTap() | Servidor por proceso de trabajo, buzón por prueba |
startInboxTapFixture() usa 0 en ambos puertos de forma predeterminada y devuelve server,
client, un transporte verificado de Nodemailer, los datos de conexión smtp, createInbox() y
close(). Todos los adaptadores usan el mismo objeto de recursos. Instala Nodemailer 9 y, además,
Vitest 4.1 o Playwright 1.61 cuando uses sus rutas secundarias específicas. El valor smtp
contiene
{ host, port, secure: false, ignoreTLS: true } para configurar la aplicación que se está probando.
Crea un buzón nuevo para cada prueba. Un recurso puede compartir el servidor de captura y el transporte dentro de su alcance documentado, pero un buzón global para todo el conjunto permitiría que las pruebas paralelas leyeran el mismo destinatario.
Puntos de entrada para comparadores
Los comparadores de aserción se publican por separado del servidor, el cliente y los recursos de prueba:
| Importación | Exportaciones principales | Comportamiento |
|---|---|---|
inboxtap/matchers | inboxTapMatchers, createInboxTapMatchers() | Implementaciones sin dependencias opcionales y tipos públicos de observación |
inboxtap/matchers/bun | extendInboxTapExpect(expect) | Amplía en el mismo lugar el expect inyectado por Bun |
inboxtap/matchers/vitest | extendInboxTapExpect(expect) | Amplía en el mismo lugar el expect inyectado por Vitest |
inboxtap/matchers/playwright | extendInboxTapExpect(expect) | Devuelve un nuevo expect tipado de Playwright |
Importar inboxtap, inboxtap/client o inboxtap/matchers no requiere Nodemailer, Vitest ni
Playwright. Solo un adaptador específico de un ejecutor necesita la dependencia correspondiente.
import { expect } from "vitest";
import { extendInboxTapExpect } from "inboxtap/matchers/vitest";
extendInboxTapExpect(expect);
await expect(inbox).toHaveDeliveredOnce({
subject: /password reset/i,
quietMs: 100,
});
const email = await inbox.waitForMessage({ subject: /password reset/i });
expect(email).toHaveRecipient(inbox.address);
expect(email).toContainLink("/reset-password");
expect(email).toHaveUnsubscribeHeader({ oneClick: true });| Comparador | Semántica |
|---|---|
toHaveDeliveredOnce({ subject?, quietMs? }) | Toma una instantánea inmediata del buzón y exige exactamente un mensaje coincidente. Un valor subject de tipo cadena se interpreta como una subcadena sin distinguir mayúsculas; una expresión regular se prueba sin cambiar su lastIndex. quietMs es un entero de 0 a 60000 y su valor predeterminado es 0. |
toHaveRecipient(address) | Compara de forma exacta y sin distinguir mayúsculas los destinatarios del sobre SMTP; no usa el campo de dirección de presentación analizado. |
toContainLink(stringOrRegex) | Comprueba los enlaces HTTP(S) extraídos. Una cadena se interpreta como una subcadena; una expresión regular clonada mantiene intacto el estado del llamador. |
toHaveUnsubscribeHeader({ oneClick? }) | Analiza solo las cabeceras RFC en bruto de nivel superior, incluidos nombres con distintas mayúsculas y valores plegados. Por defecto exige un List-Unsubscribe no vacío; con oneClick: true, también exige un destino HTTPS entre corchetes angulares y el valor exacto, sin distinguir mayúsculas, List-Unsubscribe-Post: List-Unsubscribe=One-Click. |
toHaveDeliveredOnce() no espera a que llegue un mensaje inicial. Cuando su primera instantánea es válida,
quietMs observa solo el intervalo siguiente; no demuestra que sea imposible un reintento
posterior. El comparador de un solo clic solo comprueba la forma de las cabeceras RFC 8058. No
verifica firmas DKIM, la cobertura de la firma ni el comportamiento del punto de acceso para
cancelar la suscripción.
Los resultados de los comparadores omiten deliberadamente actual, expected, los cuerpos del
mensaje, los valores de destinatario y enlace, los patrones con tokens y las cabeceras en bruto.
Los fallos solo exponen conteos y estados booleanos seguros.
Para recopilar un informe, pasa un recopilador síncrono a la función de creación:
import {
createInboxTapMatchers,
type InboxTapMatcherObservation,
} from "inboxtap/matchers";
const observations: InboxTapMatcherObservation[] = [];
const matchers = createInboxTapMatchers({
recorder: {
recordMatcherObservation(observation) {
observations.push(observation);
},
},
});Cada observación tiene una versión de esquema y registra el nombre del comparador, la negación y el
estado de éxito, el ID opcional del mensaje capturado y conteos o booleanos sin contenido sensible.
Nunca contiene el destinatario esperado, el asunto, el patrón del enlace, el valor de una cabecera
ni el cuerpo capturado. Pasa matchers al método expect.extend() de un ejecutor compatible; los
adaptadores específicos de cada ejecutor llaman a la misma función de creación.
Informes de prueba
Importa InboxTapReport desde la ruta secundaria inboxtap/reports, que no requiere dependencias
opcionales. El recopilador implementa InboxTapMatcherRecorder, por lo que puedes pasarlo
directamente a un adaptador de ejecutor o a
createInboxTapMatchers({ recorder }). Añade explícitamente los mensajes capturados y las
aserciones de la aplicación:
import { test as base } from "vitest";
import { extendInboxTap } from "inboxtap/fixtures/vitest";
import { extendInboxTapExpect } from "inboxtap/matchers/vitest";
import { InboxTapReport } from "inboxtap/reports";
const test = extendInboxTap(base);
test("writes redacted evidence", async ({ expect, inboxTap, inbox }) => {
const report = new InboxTapReport({ title: "Signup email" });
extendInboxTapExpect(expect, { recorder: report });
try {
await inboxTap.transport.sendMail({
from: "app@local.test",
to: inbox.address,
subject: "Verify your account",
text: "Open https://app.local.test/verify/id-example?next=private",
});
await expect(inbox).toHaveDeliveredOnce({ subject: /verify/i });
const email = await inbox.waitForMessage({ subject: /verify/i });
report.addAssertion({
name: "verification email exposes one link",
passed: email.links.length === 1,
messageId: email.id,
});
} finally {
for (const email of await inbox.messages()) report.addMessage(email);
await report.write("artifacts/signup-email.json");
await report.write("artifacts/signup-email.html");
}
});Escribir desde finally conserva la evidencia más reciente cuando falla un comparador o una aserción
de la aplicación.
| Método | Comportamiento |
|---|---|
addMessage(message) | Añade un CapturedEmail, elimina duplicados por ID y devuelve el recopilador para encadenar llamadas. |
addAssertion({ name, passed, message?, messageId?, details? }) | Añade una aserción de la aplicación y devuelve el recopilador. Las cadenas opcionales y los valores estructurados de details pasan por el mismo proceso acotado para ocultar datos sensibles. |
recordMatcherObservation(observation) | Implementa InboxTapMatcherRecorder; los adaptadores de los ejecutores lo llaman por cada comparador completado. |
render({ format: "json" | "html" }) | Devuelve el artefacto determinista como cadena. |
write(filePath, { format? }) | Escribe el artefacto de forma asíncrona e infiere el formato desde la extensión cuando se omite. |
render({ format }) devuelve JSON determinista o HTML estático y autónomo para las mismas
entradas ordenadas. El JSON incluye una versión de esquema. write(filePath, { format? }) infiere
el formato a partir de una extensión .json o .html cuando se omite y crea los directorios padre
que falten. Ninguna de las dos operaciones llama al servidor de InboxTap.
El documento JSON de versión 1 expone title, protection, summary, messages, assertions y
truncation. El truncamiento registra los conteos de mensajes y aserciones omitidos, los campos
acortados, los bytes UTF-8 omitidos y el límite de bytes del artefacto.
utf8BytesOmittedExact vale true cuando utf8BytesOmitted es exacto. Vale false cuando un
recorrido acotado termina antes de inspeccionar todo el valor; en ese caso, el conteo es un límite
inferior medido; el HTML distingue ambos casos mediante los valores exactly y at least.
El ámbito del recopilador sigue al objeto expect ampliado. El ejemplo usa el objeto expect de
Vitest vinculado a la prueba, de modo que cada informe recibe solo las observaciones de los
comparadores de esa
prueba. No conectes recopiladores distintos por prueba a un mismo expect compartido de Bun o
Vitest mientras las pruebas se ejecutan en paralelo. En ese caso, usa llamadas explícitas a
addMessage() y addAssertion(), o recopila deliberadamente un único informe para el conjunto de
pruebas. El
adaptador de Playwright devuelve un nuevo expect ampliado que puede limitarse a un informe.
El proceso predeterminado para ocultar datos sensibles:
- excluye la fuente RFC en bruto;
- asigna a cada dirección de correo un seudónimo coherente dentro del informe;
- elimina las credenciales de las URL y oculta todos los valores de consulta, los fragmentos, los
valores de ruta similares a secretos,
Authorization,Cookie,Proxy-Authorization,Set-Cookie,X-API-Key,X-Auth-Tokeny los valores similares a tokens en texto y HTML; y - escapa el HTML capturado en vez de representarlo y nunca ejecuta scripts capturados ni carga imágenes, estilos o píxeles de seguimiento capturados o remotos.
Usa redaction.patterns y redaction.additionalSensitiveHeaders para secretos específicos del
proyecto. Si un patrón personalizado se solapa con una URL, InboxTap sustituye la URL completa para
que una mutación mediante expresiones regulares no exponga valores adyacentes de consulta o
fragmento. La recopilación está limitada a 100 mensajes y 1.000 aserciones, y cada artefacto
generado tiene un máximo de 10 MiB. Cuando se alcanza un límite, la salida registra marcadores de
truncamiento explícitos en vez de crecer sin límite.
La ocultación de datos sensibles reduce el riesgo, pero no puede garantizar que detecte cualquier
dato personal o secreto. Revisa el artefacto antes de compartirlo. includeRaw: true conserva una
copia protegida de la fuente RFC en bruto y solo debe usarse cuando el valor diagnóstico adicional
justifique un riesgo de divulgación sustancialmente mayor.
Controlador de fallos SMTP
Cada servidor programático expone un SmtpFaultController como server.faults; quienes usan
recursos de prueba acceden a él mediante inboxTap.server.faults. Los tipos públicos del
controlador, las opciones, la compuerta y el estado se exportan desde inboxtap. No hace falta
ninguna opción de activación.
interface SmtpFaultController {
failNext(options: SmtpFailNextOptions): void;
delayNext(options: SmtpDelayNextOptions): void;
disconnectNext(options: SmtpDisconnectNextOptions): void;
pauseNext(options?: SmtpPauseNextOptions): SmtpPauseGate;
reset(): void;
}Registra una regla antes de activar la aplicación. Por orden de registro, una regla se vincula a la
siguiente transacción SMTP coincidente que alcance DATA, y solo se aplica una regla a cada
transacción. Si se omite to, coincide cualquier destinatario; en caso contrario, la comparación
no distingue mayúsculas en el sobre SMTP. Si cualquier destinatario coincide en una transacción
con varios destinatarios, el fallo se aplica a toda la transacción. Los procesos de escucha SMTP
de IPv4 e IPv6 consumen la misma cola del controlador.
| Método | Opciones y límites | Resultado |
|---|---|---|
failNext | code entero de 400 a 599; message opcional sin CR/LF, con valor predeterminado Injected SMTP failure; to y times opcionales | Devuelve el fallo SMTP configurado en vez de capturar el mensaje |
delayNext | durationMs entero de 0 a 60000; to y times opcionales | Retiene la transacción antes de completar la entrega |
disconnectNext | afterBytes entero seguro no negativo; to y times opcionales | Cierra la conexión SMTP después de observar el umbral |
pauseNext | to opcional; timeoutMs entero de 0 a 60000, con valor predeterminado 60000; sin opción times | Devuelve un SmtpPauseGate controlado de forma independiente |
reset | Sin opciones | Aborta las esperas en cola y activas, y limpia sus temporizadores |
times debe ser un entero de 1 a 100 y tiene por defecto 1. Las aplicaciones expandidas de
las reglas comparten un límite de 100, contando las que están en cola y las activas. Un to
proporcionado debe ser una cadena no vacía; se ignoran los espacios alrededor. Los umbrales de
desconexión se observan en los límites de los fragmentos del flujo, en vez de byte por byte. InboxTap cierra
al final de DATA cuando un mensaje es más corto que afterBytes, para que la transacción
inyectada falle de todos modos. Las entregas fallidas o desconectadas nunca entran en el almacén.
Las entregas retrasadas o pausadas solo aparecen después de completar correctamente la entrega
SMTP.
SmtpPauseGate.state es un SmtpPauseState de solo lectura:
| Estado | Significado |
|---|---|
pending | La regla está en cola y no ha alcanzado un comando DATA coincidente |
paused | Una transacción SMTP coincidente está retenida |
released | release() canceló una regla pendiente o reanudó una transacción pausada |
expired | Se agotó el tiempo de espera absoluto; una transacción activa recibe SMTP 451 y no se captura |
aborted | reset() o la parada del servidor canceló la compuerta |
El tiempo de espera empieza al llamar a pauseNext(), por lo que acota tanto el tiempo en cola como el de
pausa activa. La compuerta pasa a paused después de que el cuerpo del mensaje supere el análisis
y la validación de tamaño y entre en la espera. waitUntilPaused() se resuelve entonces y
permanece resuelta aunque la compuerta termine después. Rechaza si la compuerta alcanza un estado
terminal antes de pausarse. release() es idempotente y puede llamarse durante pending; al
hacerlo, elimina la regla en cola para que una transacción posterior no resulte afectada.
const gate = server.faults.pauseNext({
to: inbox.address,
timeoutMs: 10_000,
});
const delivery = triggerEmail(inbox.address);
await gate.waitUntilPaused();
expect(gate.state).toBe("paused");
gate.release();
await delivery;reset() no borra los mensajes capturados. La parada del servidor proporciona la misma limpieza
acotada de las compuertas: las que están en cola o activas pasan a aborted y se limpian sus
temporizadores. Los controles de fallos son solo programáticos: no existen rutas HTTP, opciones de
CLI, historial ni estadísticas de fallos.
Métodos de TestInbox
| Método | Se resuelve a | Propósito |
|---|---|---|
messages() | CapturedEmail[] | Lista los mensajes de esta dirección |
clear() | number | Borra los mensajes de esta dirección |
waitForMessage(options) | CapturedEmail | Espera el mensaje coincidente completo |
waitForLink(options) | string | Espera el primer enlace, opcionalmente filtrado por texto contenido |
waitForCode(options) | string | Espera un código que coincida con un patrón; por defecto seis dígitos |
waitForMatch(options) | string | Espera una coincidencia obligatoria de cadena o expresión regular |
Las opciones de espera aceptan subject, timeoutMs y afterId. subject puede ser una
subcadena sin distinguir mayúsculas o una expresión regular. Las esperas de extracción
tienen un valor por defecto de 10 segundos.
const code = await inbox.waitForCode({
pattern: /\b\d{4}\b/,
subject: /sign in/i,
timeoutMs: 20_000,
});Métodos de bajo nivel
InboxTapClient también expone health, listEmails, latestEmail, waitForEmail,
clearEmails y el método genérico request. Prefiere TestInbox dentro de pruebas en paralelo
porque aplica automáticamente el filtro de destinatario único.
CapturedEmail
interface CapturedEmail {
id: string;
receivedAt: string;
envelope: { from: string | null; to: string[] };
from: string;
to: string[];
subject: string;
headers: Record<string, string>;
text: string;
html: string;
links: string[];
codes: string[];
raw: string;
}links y codes son listas. InboxTap no asigna nombres semánticos como magic o verification
a los valores extraídos.
Errores
Las peticiones fallidas y los tiempos de espera agotados durante la extracción lanzan
InboxTapError. Su propiedad status contiene el estado al estilo HTTP, incluido 408 cuando se
agota el tiempo de espera y 404 cuando el mensaje no existe.