@arizeai/phoenix-client 6.10.1 → 6.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -0
- package/dist/esm/__generated__/api/v1.d.ts +254 -2
- package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
- package/dist/esm/jest/index.d.ts +5 -0
- package/dist/esm/jest/index.d.ts.map +1 -0
- package/dist/esm/jest/index.js +49 -0
- package/dist/esm/jest/index.js.map +1 -0
- package/dist/esm/jest/reporter.d.ts +13 -0
- package/dist/esm/jest/reporter.d.ts.map +1 -0
- package/dist/esm/jest/reporter.js +19 -0
- package/dist/esm/jest/reporter.js.map +1 -0
- package/dist/esm/prompts/sdks/toAI.d.ts +2 -2
- package/dist/esm/prompts/sdks/toAI.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toAI.js.map +1 -1
- package/dist/esm/prompts/sdks/toAnthropic.d.ts +2 -2
- package/dist/esm/prompts/sdks/toAnthropic.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toAnthropic.js.map +1 -1
- package/dist/esm/prompts/sdks/toOpenAI.d.ts +2 -2
- package/dist/esm/prompts/sdks/toOpenAI.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toOpenAI.js.map +1 -1
- package/dist/esm/prompts/sdks/toSDK.d.ts +8 -8
- package/dist/esm/prompts/sdks/toSDK.d.ts.map +1 -1
- package/dist/esm/prompts/sdks/toSDK.js.map +1 -1
- package/dist/esm/prompts/sdks/types.d.ts +2 -2
- package/dist/esm/prompts/sdks/types.d.ts.map +1 -1
- package/dist/esm/schemas/llm/anthropic/converters.d.ts +8 -8
- package/dist/esm/schemas/llm/anthropic/messagePartSchemas.d.ts +4 -4
- package/dist/esm/schemas/llm/anthropic/messageSchemas.d.ts +6 -6
- package/dist/esm/schemas/llm/constants.d.ts +3 -3
- package/dist/esm/schemas/llm/converters.d.ts +12 -12
- package/dist/esm/schemas/llm/openai/converters.d.ts +3 -3
- package/dist/esm/schemas/llm/schemas.d.ts +2 -2
- package/dist/esm/testing/acceptance.d.ts +20 -0
- package/dist/esm/testing/acceptance.d.ts.map +1 -0
- package/dist/esm/testing/acceptance.js +129 -0
- package/dist/esm/testing/acceptance.js.map +1 -0
- package/dist/esm/testing/define-api.d.ts +157 -0
- package/dist/esm/testing/define-api.d.ts.map +1 -0
- package/dist/esm/testing/define-api.js +78 -0
- package/dist/esm/testing/define-api.js.map +1 -0
- package/dist/esm/testing/helpers.d.ts +55 -0
- package/dist/esm/testing/helpers.d.ts.map +1 -0
- package/dist/esm/testing/helpers.js +179 -0
- package/dist/esm/testing/helpers.js.map +1 -0
- package/dist/esm/testing/phoenix-test-tracking.d.ts +68 -0
- package/dist/esm/testing/phoenix-test-tracking.d.ts.map +1 -0
- package/dist/esm/testing/phoenix-test-tracking.js +521 -0
- package/dist/esm/testing/phoenix-test-tracking.js.map +1 -0
- package/dist/esm/testing/report-artifacts.d.ts +45 -0
- package/dist/esm/testing/report-artifacts.d.ts.map +1 -0
- package/dist/esm/testing/report-artifacts.js +218 -0
- package/dist/esm/testing/report-artifacts.js.map +1 -0
- package/dist/esm/testing/report-run.d.ts +22 -0
- package/dist/esm/testing/report-run.d.ts.map +1 -0
- package/dist/esm/testing/report-run.js +41 -0
- package/dist/esm/testing/report-run.js.map +1 -0
- package/dist/esm/testing/reporter-format.d.ts +83 -0
- package/dist/esm/testing/reporter-format.d.ts.map +1 -0
- package/dist/esm/testing/reporter-format.js +852 -0
- package/dist/esm/testing/reporter-format.js.map +1 -0
- package/dist/esm/testing/runner.d.ts +31 -0
- package/dist/esm/testing/runner.d.ts.map +1 -0
- package/dist/esm/testing/runner.js +238 -0
- package/dist/esm/testing/runner.js.map +1 -0
- package/dist/esm/testing/state.d.ts +138 -0
- package/dist/esm/testing/state.d.ts.map +1 -0
- package/dist/esm/testing/state.js +31 -0
- package/dist/esm/testing/state.js.map +1 -0
- package/dist/esm/testing/types.d.ts +319 -0
- package/dist/esm/testing/types.d.ts.map +1 -0
- package/dist/esm/testing/types.js +9 -0
- package/dist/esm/testing/types.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/utils/channel.d.ts +7 -7
- package/dist/esm/utils/channel.d.ts.map +1 -1
- package/dist/esm/utils/channel.js +1 -1
- package/dist/esm/utils/channel.js.map +1 -1
- package/dist/esm/utils/formatPromptMessages.d.ts.map +1 -1
- package/dist/esm/utils/getPromptBySelector.d.ts.map +1 -1
- package/dist/esm/utils/promisifyResult.d.ts +1 -1
- package/dist/esm/utils/promisifyResult.d.ts.map +1 -1
- package/dist/esm/utils/promisifyResult.js.map +1 -1
- package/dist/esm/utils/schemaMatches.d.ts +5 -5
- package/dist/esm/utils/schemaMatches.d.ts.map +1 -1
- package/dist/esm/utils/schemaMatches.js.map +1 -1
- package/dist/esm/vitest/index.d.ts +5 -0
- package/dist/esm/vitest/index.d.ts.map +1 -0
- package/dist/esm/vitest/index.js +15 -0
- package/dist/esm/vitest/index.js.map +1 -0
- package/dist/esm/vitest/reporter.d.ts +19 -0
- package/dist/esm/vitest/reporter.d.ts.map +1 -0
- package/dist/esm/vitest/reporter.js +27 -0
- package/dist/esm/vitest/reporter.js.map +1 -0
- package/dist/src/__generated__/api/v1.d.ts +254 -2
- package/dist/src/__generated__/api/v1.d.ts.map +1 -1
- package/dist/src/jest/index.d.ts +5 -0
- package/dist/src/jest/index.d.ts.map +1 -0
- package/dist/src/jest/index.js +58 -0
- package/dist/src/jest/index.js.map +1 -0
- package/dist/src/jest/reporter.d.ts +13 -0
- package/dist/src/jest/reporter.d.ts.map +1 -0
- package/dist/src/jest/reporter.js +23 -0
- package/dist/src/jest/reporter.js.map +1 -0
- package/dist/src/prompts/sdks/toAI.d.ts +2 -2
- package/dist/src/prompts/sdks/toAI.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toAI.js.map +1 -1
- package/dist/src/prompts/sdks/toAnthropic.d.ts +2 -2
- package/dist/src/prompts/sdks/toAnthropic.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toAnthropic.js.map +1 -1
- package/dist/src/prompts/sdks/toOpenAI.d.ts +2 -2
- package/dist/src/prompts/sdks/toOpenAI.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toOpenAI.js.map +1 -1
- package/dist/src/prompts/sdks/toSDK.d.ts +8 -8
- package/dist/src/prompts/sdks/toSDK.d.ts.map +1 -1
- package/dist/src/prompts/sdks/toSDK.js.map +1 -1
- package/dist/src/prompts/sdks/types.d.ts +2 -2
- package/dist/src/prompts/sdks/types.d.ts.map +1 -1
- package/dist/src/schemas/llm/anthropic/converters.d.ts +8 -8
- package/dist/src/schemas/llm/anthropic/messagePartSchemas.d.ts +4 -4
- package/dist/src/schemas/llm/anthropic/messageSchemas.d.ts +6 -6
- package/dist/src/schemas/llm/constants.d.ts +3 -3
- package/dist/src/schemas/llm/converters.d.ts +12 -12
- package/dist/src/schemas/llm/openai/converters.d.ts +3 -3
- package/dist/src/schemas/llm/schemas.d.ts +2 -2
- package/dist/src/testing/acceptance.d.ts +20 -0
- package/dist/src/testing/acceptance.d.ts.map +1 -0
- package/dist/src/testing/acceptance.js +114 -0
- package/dist/src/testing/acceptance.js.map +1 -0
- package/dist/src/testing/define-api.d.ts +157 -0
- package/dist/src/testing/define-api.d.ts.map +1 -0
- package/dist/src/testing/define-api.js +81 -0
- package/dist/src/testing/define-api.js.map +1 -0
- package/dist/src/testing/helpers.d.ts +55 -0
- package/dist/src/testing/helpers.d.ts.map +1 -0
- package/dist/src/testing/helpers.js +182 -0
- package/dist/src/testing/helpers.js.map +1 -0
- package/dist/src/testing/phoenix-test-tracking.d.ts +68 -0
- package/dist/src/testing/phoenix-test-tracking.d.ts.map +1 -0
- package/dist/src/testing/phoenix-test-tracking.js +530 -0
- package/dist/src/testing/phoenix-test-tracking.js.map +1 -0
- package/dist/src/testing/report-artifacts.d.ts +45 -0
- package/dist/src/testing/report-artifacts.d.ts.map +1 -0
- package/dist/src/testing/report-artifacts.js +225 -0
- package/dist/src/testing/report-artifacts.js.map +1 -0
- package/dist/src/testing/report-run.d.ts +22 -0
- package/dist/src/testing/report-run.d.ts.map +1 -0
- package/dist/src/testing/report-run.js +47 -0
- package/dist/src/testing/report-run.js.map +1 -0
- package/dist/src/testing/reporter-format.d.ts +83 -0
- package/dist/src/testing/reporter-format.d.ts.map +1 -0
- package/dist/src/testing/reporter-format.js +870 -0
- package/dist/src/testing/reporter-format.js.map +1 -0
- package/dist/src/testing/runner.d.ts +31 -0
- package/dist/src/testing/runner.d.ts.map +1 -0
- package/dist/src/testing/runner.js +258 -0
- package/dist/src/testing/runner.js.map +1 -0
- package/dist/src/testing/state.d.ts +138 -0
- package/dist/src/testing/state.d.ts.map +1 -0
- package/dist/src/testing/state.js +38 -0
- package/dist/src/testing/state.js.map +1 -0
- package/dist/src/testing/types.d.ts +319 -0
- package/dist/src/testing/types.d.ts.map +1 -0
- package/dist/src/testing/types.js +13 -0
- package/dist/src/testing/types.js.map +1 -0
- package/dist/src/utils/channel.d.ts +7 -7
- package/dist/src/utils/channel.d.ts.map +1 -1
- package/dist/src/utils/channel.js +1 -1
- package/dist/src/utils/channel.js.map +1 -1
- package/dist/src/utils/formatPromptMessages.d.ts.map +1 -1
- package/dist/src/utils/getPromptBySelector.d.ts.map +1 -1
- package/dist/src/utils/promisifyResult.d.ts +1 -1
- package/dist/src/utils/promisifyResult.d.ts.map +1 -1
- package/dist/src/utils/promisifyResult.js.map +1 -1
- package/dist/src/utils/schemaMatches.d.ts +5 -5
- package/dist/src/utils/schemaMatches.d.ts.map +1 -1
- package/dist/src/utils/schemaMatches.js.map +1 -1
- package/dist/src/vitest/index.d.ts +5 -0
- package/dist/src/vitest/index.d.ts.map +1 -0
- package/dist/src/vitest/index.js +23 -0
- package/dist/src/vitest/index.js.map +1 -0
- package/dist/src/vitest/reporter.d.ts +19 -0
- package/dist/src/vitest/reporter.d.ts.map +1 -0
- package/dist/src/vitest/reporter.js +34 -0
- package/dist/src/vitest/reporter.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/ci-evals-annotations.mdx +190 -0
- package/docs/ci-evals-jest.mdx +78 -0
- package/docs/ci-evals-vitest.mdx +240 -0
- package/docs/ci-evals.mdx +263 -0
- package/docs/overview.mdx +9 -1
- package/package.json +49 -17
- package/src/__generated__/api/v1.ts +254 -2
- package/src/jest/index.ts +124 -0
- package/src/jest/reporter.ts +22 -0
- package/src/prompts/sdks/toAI.ts +4 -3
- package/src/prompts/sdks/toAnthropic.ts +4 -3
- package/src/prompts/sdks/toOpenAI.ts +4 -3
- package/src/prompts/sdks/toSDK.ts +16 -11
- package/src/prompts/sdks/types.ts +2 -2
- package/src/testing/acceptance.ts +190 -0
- package/src/testing/define-api.ts +279 -0
- package/src/testing/helpers.ts +251 -0
- package/src/testing/phoenix-test-tracking.ts +637 -0
- package/src/testing/report-artifacts.ts +272 -0
- package/src/testing/report-run.ts +44 -0
- package/src/testing/reporter-format.ts +1072 -0
- package/src/testing/runner.ts +350 -0
- package/src/testing/state.ts +165 -0
- package/src/testing/types.ts +366 -0
- package/src/utils/channel.ts +17 -15
- package/src/utils/promisifyResult.ts +6 -4
- package/src/utils/schemaMatches.ts +12 -10
- package/src/vitest/index.ts +57 -0
- package/src/vitest/reporter.ts +32 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process bridge for the end-of-run Phoenix summary.
|
|
3
|
+
*
|
|
4
|
+
* Both Vitest and Jest run test files in separate worker processes, so a
|
|
5
|
+
* suite's results live in a different process than the reporter that prints
|
|
6
|
+
* the final summary. To bridge that gap, each worker writes a small JSON
|
|
7
|
+
* artifact describing its suite to a shared temp directory when the suite
|
|
8
|
+
* finishes (see {@link writeSuiteSummaryArtifact}). At the end of the run the
|
|
9
|
+
* reporter — back in the main process — reads every artifact written since the
|
|
10
|
+
* run began (see {@link readSuiteSummaryArtifacts}) and prints them together.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is best-effort: a failure to write or read an artifact must
|
|
13
|
+
* never fail a user's test, so the file system calls swallow their errors.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import {
|
|
17
|
+
existsSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
readdirSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
renameSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
statSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from "node:fs";
|
|
26
|
+
import { tmpdir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
|
|
29
|
+
import type { SuiteSummary } from "./reporter-format";
|
|
30
|
+
import type { SuiteState, TestResult } from "./state";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Env var that overrides where suite summary artifacts are written. When unset,
|
|
34
|
+
* a stable per-project subdirectory of the OS temp dir is used (see
|
|
35
|
+
* {@link getSuiteSummaryReportDir}).
|
|
36
|
+
*/
|
|
37
|
+
export const PHOENIX_TEST_REPORT_DIR_ENV_VAR = "PHOENIX_TEST_REPORT_DIR";
|
|
38
|
+
|
|
39
|
+
const ARTIFACT_VERSION = 1;
|
|
40
|
+
const ARTIFACT_FILE_PREFIX = "suite-";
|
|
41
|
+
let artifactCounter = 0;
|
|
42
|
+
|
|
43
|
+
/** The on-disk shape of a single artifact file. */
|
|
44
|
+
interface SuiteSummaryArtifact {
|
|
45
|
+
version: typeof ARTIFACT_VERSION;
|
|
46
|
+
createdAtMs: number;
|
|
47
|
+
suite: SuiteSummary;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the directory where suite summary artifacts are written and read.
|
|
52
|
+
*
|
|
53
|
+
* Honors {@link PHOENIX_TEST_REPORT_DIR_ENV_VAR} when set. Otherwise it derives
|
|
54
|
+
* a directory under the OS temp dir, namespaced by a hash of the current
|
|
55
|
+
* working directory so that concurrent test runs in different projects don't
|
|
56
|
+
* read each other's artifacts.
|
|
57
|
+
*/
|
|
58
|
+
export function getSuiteSummaryReportDir(): string {
|
|
59
|
+
const configuredReportDir = process.env[PHOENIX_TEST_REPORT_DIR_ENV_VAR];
|
|
60
|
+
if (configuredReportDir && configuredReportDir.trim().length > 0) {
|
|
61
|
+
return configuredReportDir;
|
|
62
|
+
}
|
|
63
|
+
const cwdHash = createHash("sha256")
|
|
64
|
+
.update(process.cwd())
|
|
65
|
+
.digest("hex")
|
|
66
|
+
.slice(0, 16);
|
|
67
|
+
return join(tmpdir(), "phoenix-client-test-reports", cwdHash);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Delete any artifacts left over from a previous run. The reporter calls this
|
|
72
|
+
* at the start of every run so summaries from an earlier watch-mode invocation
|
|
73
|
+
* don't bleed into the next one.
|
|
74
|
+
*/
|
|
75
|
+
export function clearSuiteSummaryArtifacts(): void {
|
|
76
|
+
const reportDir = getSuiteSummaryReportDir();
|
|
77
|
+
mkdirSync(reportDir, { recursive: true });
|
|
78
|
+
for (const fileName of readdirSync(reportDir)) {
|
|
79
|
+
if (!isArtifactFileName(fileName)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
rmSync(join(reportDir, fileName), { force: true });
|
|
84
|
+
} catch {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Write one suite's summary to the report directory. Called from each worker
|
|
92
|
+
* when a suite finishes.
|
|
93
|
+
*
|
|
94
|
+
* The file is written to a `.tmp` path first and then atomically renamed into
|
|
95
|
+
* place, so the reader never observes a half-written artifact. Failures are
|
|
96
|
+
* intentionally swallowed — reporting must not break the user's tests.
|
|
97
|
+
*/
|
|
98
|
+
export function writeSuiteSummaryArtifact(suite: SuiteState): void {
|
|
99
|
+
try {
|
|
100
|
+
const reportDir = getSuiteSummaryReportDir();
|
|
101
|
+
mkdirSync(reportDir, { recursive: true });
|
|
102
|
+
const artifact: SuiteSummaryArtifact = {
|
|
103
|
+
version: ARTIFACT_VERSION,
|
|
104
|
+
createdAtMs: Date.now(),
|
|
105
|
+
suite: createSuiteSummary(suite),
|
|
106
|
+
};
|
|
107
|
+
const fileName = `${ARTIFACT_FILE_PREFIX}${process.pid}-${Date.now()}-${artifactCounter++}.json`;
|
|
108
|
+
const artifactPath = join(reportDir, fileName);
|
|
109
|
+
const temporaryPath = `${artifactPath}.tmp`;
|
|
110
|
+
writeFileSync(temporaryPath, JSON.stringify(artifact), "utf8");
|
|
111
|
+
renameSync(temporaryPath, artifactPath);
|
|
112
|
+
} catch {
|
|
113
|
+
// Reporting artifacts are best-effort and should not fail user tests.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Read back all suite summaries the workers wrote during this run, sorted by
|
|
119
|
+
* suite name for stable output.
|
|
120
|
+
*
|
|
121
|
+
* @param sinceMs - When provided, only artifacts created at or after this
|
|
122
|
+
* timestamp are returned, so summaries from a previous run that share the
|
|
123
|
+
* report directory are ignored. A one-second slack is allowed to absorb
|
|
124
|
+
* small clock differences between worker processes.
|
|
125
|
+
*/
|
|
126
|
+
export function readSuiteSummaryArtifacts({
|
|
127
|
+
sinceMs,
|
|
128
|
+
}: {
|
|
129
|
+
sinceMs?: number;
|
|
130
|
+
} = {}): SuiteSummary[] {
|
|
131
|
+
const reportDir = getSuiteSummaryReportDir();
|
|
132
|
+
if (!existsSync(reportDir)) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const suites: SuiteSummary[] = [];
|
|
137
|
+
for (const fileName of readdirSync(reportDir)) {
|
|
138
|
+
if (
|
|
139
|
+
!fileName.startsWith(ARTIFACT_FILE_PREFIX) ||
|
|
140
|
+
!fileName.endsWith(".json")
|
|
141
|
+
) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const artifactPath = join(reportDir, fileName);
|
|
145
|
+
try {
|
|
146
|
+
const stats = statSync(artifactPath);
|
|
147
|
+
if (sinceMs !== undefined && stats.mtimeMs < sinceMs - 1000) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const artifact = JSON.parse(readFileSync(artifactPath, "utf8"));
|
|
151
|
+
if (!isSuiteSummaryArtifact(artifact)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (sinceMs !== undefined && artifact.createdAtMs < sinceMs - 1000) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
suites.push(artifact.suite);
|
|
158
|
+
} catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return suites.sort((leftSuite, rightSuite) =>
|
|
164
|
+
leftSuite.name.localeCompare(rightSuite.name)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Project the live in-memory {@link SuiteState} down to the serializable
|
|
170
|
+
* {@link SuiteSummary} that gets written to disk. Drops anything that can't or
|
|
171
|
+
* shouldn't cross the process boundary (clients, tracers, raw Error objects).
|
|
172
|
+
*/
|
|
173
|
+
function createSuiteSummary(suite: SuiteState): SuiteSummary {
|
|
174
|
+
return {
|
|
175
|
+
name: suite.name,
|
|
176
|
+
trackingDisabled: suite.trackingDisabled,
|
|
177
|
+
trackingDisabledReason: suite.trackingDisabledReason,
|
|
178
|
+
setupError: suite.setupError
|
|
179
|
+
? { message: suite.setupError.message }
|
|
180
|
+
: undefined,
|
|
181
|
+
uploadFailureCount: suite.uploadFailureCount,
|
|
182
|
+
results: suite.results.map(createTestResultSummary),
|
|
183
|
+
acceptanceResults: suite.acceptanceResults,
|
|
184
|
+
links: suite.links.map(({ label, url }) => ({ label, url })),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function createTestResultSummary(result: TestResult): TestResult {
|
|
189
|
+
return {
|
|
190
|
+
suiteName: result.suiteName,
|
|
191
|
+
testName: result.testName,
|
|
192
|
+
status: result.status,
|
|
193
|
+
output: toJsonSafeValue(result.output),
|
|
194
|
+
annotations: result.annotations.map(
|
|
195
|
+
({
|
|
196
|
+
name,
|
|
197
|
+
score,
|
|
198
|
+
label,
|
|
199
|
+
explanation,
|
|
200
|
+
annotatorKind,
|
|
201
|
+
metadata,
|
|
202
|
+
traceId,
|
|
203
|
+
}) => ({
|
|
204
|
+
name,
|
|
205
|
+
score,
|
|
206
|
+
label,
|
|
207
|
+
explanation,
|
|
208
|
+
annotatorKind,
|
|
209
|
+
metadata,
|
|
210
|
+
traceId,
|
|
211
|
+
})
|
|
212
|
+
),
|
|
213
|
+
error: result.error,
|
|
214
|
+
durationMs: result.durationMs,
|
|
215
|
+
repetitionNumber: result.repetitionNumber,
|
|
216
|
+
repetitions: result.repetitions,
|
|
217
|
+
dryRun: result.dryRun,
|
|
218
|
+
traceId: result.traceId,
|
|
219
|
+
runId: result.runId,
|
|
220
|
+
exampleId: result.exampleId,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Make a recorded test output safe to JSON-serialize: `bigint` values are
|
|
226
|
+
* stringified, and anything that still can't be serialized (cycles, etc.)
|
|
227
|
+
* falls back to its `String()` form rather than throwing.
|
|
228
|
+
*/
|
|
229
|
+
function toJsonSafeValue(value: unknown): unknown {
|
|
230
|
+
if (value === undefined) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(
|
|
235
|
+
JSON.stringify(value, (_key, nestedValue: unknown) =>
|
|
236
|
+
typeof nestedValue === "bigint" ? nestedValue.toString() : nestedValue
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
} catch {
|
|
240
|
+
return String(value);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Validate a parsed artifact before trusting it. Guards against version skew
|
|
246
|
+
* and partially-written or unrelated JSON files in the report directory.
|
|
247
|
+
*/
|
|
248
|
+
function isSuiteSummaryArtifact(value: unknown): value is SuiteSummaryArtifact {
|
|
249
|
+
if (!isRecord(value)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const suite = value.suite;
|
|
253
|
+
return (
|
|
254
|
+
value.version === ARTIFACT_VERSION &&
|
|
255
|
+
typeof value.createdAtMs === "number" &&
|
|
256
|
+
isRecord(suite) &&
|
|
257
|
+
typeof suite.name === "string" &&
|
|
258
|
+
Array.isArray(suite.results) &&
|
|
259
|
+
Array.isArray(suite.links)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isArtifactFileName(fileName: string): boolean {
|
|
264
|
+
return (
|
|
265
|
+
fileName.startsWith(ARTIFACT_FILE_PREFIX) &&
|
|
266
|
+
(fileName.endsWith(".json") || fileName.endsWith(".json.tmp"))
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
271
|
+
return typeof value === "object" && value !== null;
|
|
272
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearSuiteSummaryArtifacts,
|
|
3
|
+
readSuiteSummaryArtifacts,
|
|
4
|
+
} from "./report-artifacts";
|
|
5
|
+
import { printSuiteSummaries } from "./reporter-format";
|
|
6
|
+
import { clearAllSuites, getAllSuites } from "./runner";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shared "print the Phoenix summary once per run" policy for the jest and
|
|
10
|
+
* vitest reporters. The two frameworks differ only in which lifecycle methods
|
|
11
|
+
* they call; both delegate to {@link begin} on run start and {@link finish}
|
|
12
|
+
* on run end.
|
|
13
|
+
*/
|
|
14
|
+
export class SuiteSummaryReportRun {
|
|
15
|
+
private hasPrinted = false;
|
|
16
|
+
private runStartedAtMs = 0;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reset state at the start of a run so suites/artifacts from a previous
|
|
20
|
+
* watch-mode invocation don't leak forward.
|
|
21
|
+
*/
|
|
22
|
+
begin(): void {
|
|
23
|
+
this.hasPrinted = false;
|
|
24
|
+
this.runStartedAtMs = Date.now();
|
|
25
|
+
clearAllSuites();
|
|
26
|
+
clearSuiteSummaryArtifacts();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Print the Phoenix summary once. Prefers the on-disk artifacts written by
|
|
31
|
+
* suite workers, falling back to the in-process registry. A no-op once the
|
|
32
|
+
* summary has already been printed for this run.
|
|
33
|
+
*/
|
|
34
|
+
finish(): void {
|
|
35
|
+
if (this.hasPrinted) return;
|
|
36
|
+
const artifactSuites = readSuiteSummaryArtifacts({
|
|
37
|
+
sinceMs: this.runStartedAtMs,
|
|
38
|
+
});
|
|
39
|
+
const suites = artifactSuites.length > 0 ? artifactSuites : getAllSuites();
|
|
40
|
+
if (suites.length === 0) return;
|
|
41
|
+
this.hasPrinted = true;
|
|
42
|
+
printSuiteSummaries(suites);
|
|
43
|
+
}
|
|
44
|
+
}
|