test(e2e): align release harness coverage

This commit is contained in:
Peter Steinberger
2026-04-12 16:08:12 +01:00
parent 6c45d78e07
commit 23e50859eb
8 changed files with 223 additions and 61 deletions

View File

@@ -32,6 +32,7 @@ function createPage(opts: { targetId: string; snapshotFull?: string; hasSnapshot
context: () => context,
locator,
on: vi.fn(),
url: vi.fn(() => `https://example.test/${opts.targetId}`),
...(opts.hasSnapshotForAI === false
? {}
: {
@@ -86,8 +87,8 @@ describe("pw-ai", () => {
});
expect(res.snapshot).toBe("TWO");
expect(p1.session.detach).toHaveBeenCalledTimes(1);
expect(p2.session.detach).toHaveBeenCalledTimes(1);
expect(p1.session.detach).toHaveBeenCalled();
expect(p2.session.detach).toHaveBeenCalled();
});
it("registers aria refs from ai snapshots for act commands", async () => {

View File

@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { vi } from "vitest";
import { formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
import type { MockBaileysSocket } from "../../../test/mocks/baileys.js";
import { createMockBaileys } from "../../../test/mocks/baileys.js";
@@ -84,7 +85,11 @@ function loadSessionStoreMock(storePath: string) {
type BufferedDispatchReplyParams = {
ctx: Record<string, unknown>;
replyResolver: (ctx: Record<string, unknown>) => Promise<Record<string, unknown> | undefined>;
replyResolver: (
ctx: Record<string, unknown>,
opts?: BufferedReplyOptions,
) => Promise<Record<string, unknown> | undefined>;
replyOptions?: BufferedReplyOptions;
dispatcherOptions: {
deliver: (
payload: Record<string, unknown>,
@@ -94,32 +99,151 @@ type BufferedDispatchReplyParams = {
};
};
type MockTypingController = {
markDispatchIdle?: () => void;
markRunComplete?: () => void;
};
type BufferedReplyOptions = Record<string, unknown> & {
onTypingController?: (typing: MockTypingController) => void;
};
type TestEnvelopeOptions = {
timezone?: string;
includeTimestamp?: boolean;
includeElapsed?: boolean;
userTimezone?: string;
};
type TestInboundEnvelopeParams = {
channel?: string;
from?: string;
body: string;
timestamp?: number | Date;
chatType?: string;
senderLabel?: string;
sender?: { name?: string; e164?: string; id?: string };
previousTimestamp?: number | Date;
envelope?: TestEnvelopeOptions;
fromMe?: boolean;
};
function sanitizeEnvelopeHeaderPart(value: string) {
return value
.replace(/\r\n|\r|\n/g, " ")
.replaceAll("[", "(")
.replaceAll("]", ")")
.replace(/\s+/g, " ")
.trim();
}
function resolveEnvelopeOptionsMock(cfg?: {
agents?: {
defaults?: {
envelopeTimezone?: string;
envelopeTimestamp?: "on" | "off";
envelopeElapsed?: "on" | "off";
userTimezone?: string;
};
};
}): TestEnvelopeOptions {
const defaults = cfg?.agents?.defaults;
return {
timezone: defaults?.envelopeTimezone,
includeTimestamp: defaults?.envelopeTimestamp !== "off",
includeElapsed: defaults?.envelopeElapsed !== "off",
userTimezone: defaults?.userTimezone,
};
}
function resolveEnvelopeTimestampMock(
timestamp: number | Date | undefined,
envelope?: TestEnvelopeOptions,
) {
if (!timestamp || envelope?.includeTimestamp === false) {
return undefined;
}
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
if (Number.isNaN(date.getTime())) {
return undefined;
}
const zone = envelope?.timezone?.trim();
if (zone === "user") {
return formatEnvelopeTimestamp(date, envelope?.userTimezone?.trim() || "local");
}
return formatEnvelopeTimestamp(date, zone || "local");
}
function resolveSenderLabelMock(sender?: TestInboundEnvelopeParams["sender"]) {
const display = sender?.name?.trim();
const idPart = sender?.e164?.trim() || sender?.id?.trim();
if (display && idPart && display !== idPart) {
return `${display} (${idPart})`;
}
return display || idPart || undefined;
}
function formatInboundEnvelopeMock(params: TestInboundEnvelopeParams) {
const chatType = normalizeLowercaseStringOrEmpty(params.chatType);
const isDirect = !chatType || chatType === "direct";
const sender = params.senderLabel?.trim() || resolveSenderLabelMock(params.sender);
const body =
isDirect && params.fromMe
? `(self): ${params.body}`
: !isDirect && sender
? `${sanitizeEnvelopeHeaderPart(sender)}: ${params.body}`
: params.body;
const parts = [sanitizeEnvelopeHeaderPart(params.channel?.trim() || "Channel")];
const from = params.from?.trim();
if (from) {
parts.push(sanitizeEnvelopeHeaderPart(from));
}
const timestamp = resolveEnvelopeTimestampMock(params.timestamp, params.envelope);
if (timestamp) {
parts.push(timestamp);
}
return `[${parts.join(" ")}] ${body}`;
}
function createBufferedDispatchReplyMock() {
return vi.fn(async (params: BufferedDispatchReplyParams) => {
await params.dispatcherOptions.onReplyStart?.();
const payload = await params.replyResolver(params.ctx);
if (!payload || typeof payload !== "object") {
return {
queuedFinal: false,
counts: { tool: 0, block: 0, final: 0 },
};
}
const text = typeof payload.text === "string" ? payload.text.trim() : "";
const hasMedia =
typeof payload.mediaUrl === "string" ||
typeof payload.mediaPath === "string" ||
typeof payload.fileUrl === "string";
if (!text && !hasMedia) {
return {
queuedFinal: false,
counts: { tool: 0, block: 0, final: 0 },
};
}
await params.dispatcherOptions.deliver(payload, { kind: "final" });
return {
queuedFinal: true,
counts: { tool: 0, block: 0, final: 1 },
let typingController: MockTypingController | undefined;
const replyOptions: BufferedReplyOptions = {
...params.replyOptions,
onTypingController: (typing) => {
typingController = typing;
params.replyOptions?.onTypingController?.(typing);
},
};
await params.dispatcherOptions.onReplyStart?.();
try {
const payload = await params.replyResolver(params.ctx, replyOptions);
if (!payload || typeof payload !== "object") {
return {
queuedFinal: false,
counts: { tool: 0, block: 0, final: 0 },
};
}
const text = typeof payload.text === "string" ? payload.text.trim() : "";
const hasMedia =
typeof payload.mediaUrl === "string" ||
typeof payload.mediaPath === "string" ||
typeof payload.fileUrl === "string";
if (!text && !hasMedia) {
return {
queuedFinal: false,
counts: { tool: 0, block: 0, final: 0 },
};
}
await params.dispatcherOptions.deliver(payload, { kind: "final" });
return {
queuedFinal: true,
counts: { tool: 0, block: 0, final: 1 },
};
} finally {
typingController?.markRunComplete?.();
typingController?.markDispatchIdle?.();
}
});
}
@@ -310,8 +434,7 @@ vi.mock("./auto-reply/monitor/runtime-api.js", () => ({
}),
dispatchReplyWithBufferedBlockDispatcher: createBufferedDispatchReplyMock(),
finalizeInboundContext: <T>(ctx: T) => ctx,
formatInboundEnvelope: (params: { body: string; senderLabel?: string }) =>
`${params.senderLabel ? `${params.senderLabel}: ` : ""}${params.body}`,
formatInboundEnvelope: formatInboundEnvelopeMock,
getAgentScopedMediaLocalRoots: () => [] as string[],
jidToE164: (jid: string) => {
const digits = jid.replace(/\D+/g, "");
@@ -330,11 +453,11 @@ vi.mock("./auto-reply/monitor/runtime-api.js", () => ({
cfg.messages?.responsePrefix,
resolveInboundLastRouteSessionKey: (params: { sessionKey: string }) => params.sessionKey,
resolveInboundSessionEnvelopeContext: (params: {
cfg: { session?: { store?: string } };
cfg: { session?: { store?: string } } & Parameters<typeof resolveEnvelopeOptionsMock>[0];
agentId: string;
}) => ({
storePath: resolveStorePathFallback(params.cfg.session?.store, { agentId: params.agentId }),
envelopeOptions: {},
envelopeOptions: resolveEnvelopeOptionsMock(params.cfg),
previousTimestamp: undefined,
}),
resolveMarkdownTableMode: () => undefined,
@@ -437,13 +560,7 @@ vi.mock("./auto-reply/monitor/group-activation.runtime.js", () => ({
}));
vi.mock("./auto-reply/monitor/message-line.runtime.js", () => ({
formatInboundEnvelope: (params: {
body: string;
sender?: { name?: string; e164?: string; id?: string };
}) => {
const sender = params.sender?.name ?? params.sender?.e164 ?? params.sender?.id ?? undefined;
return sender ? `${sender}: ${params.body}` : params.body;
},
formatInboundEnvelope: formatInboundEnvelopeMock,
resolveMessagePrefix: (
cfg: {
channels?: { whatsapp?: { messagePrefix?: string; allowFrom?: string[] } };
@@ -451,7 +568,13 @@ vi.mock("./auto-reply/monitor/message-line.runtime.js", () => ({
},
_agentId: string,
params?: { configured?: string; hasAllowFrom?: boolean },
) => params?.configured ?? cfg.messages?.messagePrefix,
) => {
const configured = params?.configured ?? cfg.messages?.messagePrefix;
if (configured !== undefined) {
return configured;
}
return params?.hasAllowFrom === true ? "" : "[openclaw]";
},
}));
vi.mock("./auth-store.runtime.js", () => ({