@tooluminati/testing 0.1.0

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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser-helpers-RSQLRHHZ.js +8 -0
  3. package/dist/browser-helpers.d.ts +6 -0
  4. package/dist/browser-helpers.d.ts.map +1 -0
  5. package/dist/browser-helpers.js +23 -0
  6. package/dist/chunk-6YAIOYYZ.js +30 -0
  7. package/dist/chunk-EG3BG65X.js +146 -0
  8. package/dist/comparative-proof-QUN7C3KM.js +11 -0
  9. package/dist/comparative-proof.d.ts +22 -0
  10. package/dist/comparative-proof.d.ts.map +1 -0
  11. package/dist/comparative-proof.js +29 -0
  12. package/dist/devtools-mcp-helper.d.ts +17 -0
  13. package/dist/devtools-mcp-helper.d.ts.map +1 -0
  14. package/dist/devtools-mcp-helper.js +34 -0
  15. package/dist/dom-only-inspection.d.ts +13 -0
  16. package/dist/dom-only-inspection.d.ts.map +1 -0
  17. package/dist/dom-only-inspection.js +86 -0
  18. package/dist/dom-only-inspection.test.d.ts +2 -0
  19. package/dist/dom-only-inspection.test.d.ts.map +1 -0
  20. package/dist/dom-only-inspection.test.js +25 -0
  21. package/dist/index.cjs +473 -0
  22. package/dist/index.d.ts +12 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +245 -0
  25. package/dist/install-model-context-mock.d.ts +3 -0
  26. package/dist/install-model-context-mock.d.ts.map +1 -0
  27. package/dist/install-model-context-mock.js +10 -0
  28. package/dist/mock-model-context.d.ts +14 -0
  29. package/dist/mock-model-context.d.ts.map +1 -0
  30. package/dist/mock-model-context.js +45 -0
  31. package/dist/model-context-mock-script.d.ts +2 -0
  32. package/dist/model-context-mock-script.d.ts.map +1 -0
  33. package/dist/model-context-mock-script.js +40 -0
  34. package/dist/webmcp-evals.d.ts +32 -0
  35. package/dist/webmcp-evals.d.ts.map +1 -0
  36. package/dist/webmcp-evals.js +81 -0
  37. package/package.json +50 -0
package/dist/index.js ADDED
@@ -0,0 +1,245 @@
1
+ import {
2
+ collectDisabledActionReasonsFromDom,
3
+ diagnoseCheckoutBlockerFromDomOnly,
4
+ diagnoseCheckoutBlockerFromWebMcp,
5
+ inspectDisabledActionFromDom,
6
+ runComparativeProof
7
+ } from "./chunk-EG3BG65X.js";
8
+ import {
9
+ expectWebMcpTool,
10
+ invokeWebMcpTool
11
+ } from "./chunk-6YAIOYYZ.js";
12
+
13
+ // src/devtools-mcp-helper.ts
14
+ function getModelContext(host = document) {
15
+ return host.modelContext;
16
+ }
17
+ async function listWebMcpTools(host = document) {
18
+ const context = getModelContext(host);
19
+ if (!context?.getTools) {
20
+ return [];
21
+ }
22
+ const tools = await context.getTools();
23
+ return tools.map((tool) => ({
24
+ name: tool.name,
25
+ description: tool.description
26
+ }));
27
+ }
28
+ async function executeWebMcpTool(name, args = {}, host = document) {
29
+ const context = getModelContext(host);
30
+ if (!context?.getTools || !context.executeTool) {
31
+ throw new Error(
32
+ "WebMCP model context is unavailable. Use installModelContextMock() in tests."
33
+ );
34
+ }
35
+ const tools = await context.getTools();
36
+ const tool = tools.find((candidate) => candidate.name === name);
37
+ if (!tool) {
38
+ throw new Error(`WebMCP tool not found: ${name}`);
39
+ }
40
+ return context.executeTool(tool, JSON.stringify(args));
41
+ }
42
+
43
+ // src/mock-model-context.ts
44
+ var MockModelContext = class extends EventTarget {
45
+ tools = /* @__PURE__ */ new Map();
46
+ invocations = [];
47
+ registerTool(tool, options = {}) {
48
+ if (this.tools.has(tool.name)) {
49
+ throw new Error(`Tool already registered: ${tool.name}`);
50
+ }
51
+ if (options.signal?.aborted) {
52
+ return;
53
+ }
54
+ this.tools.set(tool.name, tool);
55
+ this.dispatchEvent(new Event("toolchange"));
56
+ options.signal?.addEventListener(
57
+ "abort",
58
+ () => {
59
+ this.tools.delete(tool.name);
60
+ this.dispatchEvent(new Event("toolchange"));
61
+ },
62
+ { once: true }
63
+ );
64
+ }
65
+ async getTools() {
66
+ return [...this.tools.values()];
67
+ }
68
+ async executeTool(toolOrName, argsJson = "{}") {
69
+ const name = typeof toolOrName === "string" ? toolOrName : toolOrName.name;
70
+ if (!name) {
71
+ throw new Error("Tool name is required.");
72
+ }
73
+ const tool = this.tools.get(name);
74
+ if (!tool) {
75
+ throw new Error(`Tool not found: ${name}`);
76
+ }
77
+ const args = JSON.parse(argsJson);
78
+ try {
79
+ const result = await tool.execute(args, {
80
+ signal: new AbortController().signal
81
+ });
82
+ this.invocations.push({ name, args, result });
83
+ return result;
84
+ } catch (error) {
85
+ this.invocations.push({ name, args, error });
86
+ throw error;
87
+ }
88
+ }
89
+ };
90
+
91
+ // src/install-model-context-mock.ts
92
+ function installModelContextMock(target = "document") {
93
+ const mock = new MockModelContext();
94
+ const host = target === "document" ? document : navigator;
95
+ Object.defineProperty(host, "modelContext", {
96
+ configurable: true,
97
+ value: mock
98
+ });
99
+ return mock;
100
+ }
101
+
102
+ // src/model-context-mock-script.ts
103
+ var MODEL_CONTEXT_MOCK_INIT_SCRIPT = `
104
+ (() => {
105
+ const tools = new Map();
106
+
107
+ Object.defineProperty(document, 'modelContext', {
108
+ configurable: true,
109
+ value: {
110
+ registerTool(tool, options = {}) {
111
+ if (options.signal?.aborted) {
112
+ return;
113
+ }
114
+
115
+ tools.set(tool.name, tool);
116
+ options.signal?.addEventListener(
117
+ 'abort',
118
+ () => {
119
+ tools.delete(tool.name);
120
+ },
121
+ { once: true },
122
+ );
123
+ },
124
+ async getTools() {
125
+ return [...tools.values()];
126
+ },
127
+ async executeTool(toolOrName, argsJson = '{}') {
128
+ const name =
129
+ typeof toolOrName === 'string'
130
+ ? toolOrName
131
+ : toolOrName?.name;
132
+ const tool = name ? tools.get(name) : undefined;
133
+ if (!tool) {
134
+ throw new Error('Tool not found: ' + name);
135
+ }
136
+
137
+ return tool.execute(JSON.parse(argsJson));
138
+ },
139
+ },
140
+ });
141
+ })();
142
+ `;
143
+
144
+ // src/webmcp-evals.ts
145
+ var BUDGETS = {
146
+ name: 30,
147
+ paramName: 30,
148
+ paramDescription: 150,
149
+ description: 500,
150
+ output: 1500
151
+ };
152
+ function walkSchemaProperties(schema, visit) {
153
+ if (!schema || typeof schema !== "object") {
154
+ return;
155
+ }
156
+ const node = schema;
157
+ const properties = node.properties;
158
+ if (properties && typeof properties === "object") {
159
+ for (const [name, child] of Object.entries(properties)) {
160
+ if (child && typeof child === "object") {
161
+ visit(name, child);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ async function snapshotRegisteredTools(page) {
167
+ return page.evaluate(() => {
168
+ const context = document.modelContext;
169
+ if (!context?.getTools) {
170
+ return [];
171
+ }
172
+ return context.getTools().then(
173
+ (tools) => tools.map((tool) => ({
174
+ name: String(tool.name ?? ""),
175
+ description: String(tool.description ?? ""),
176
+ inputSchema: tool.inputSchema,
177
+ ...tool.annotations ? { annotations: tool.annotations } : {}
178
+ }))
179
+ );
180
+ });
181
+ }
182
+ function createToolCallEvalFixture(tools, cases) {
183
+ return { tools, cases };
184
+ }
185
+ function assertToolSchemaBudgets(tools) {
186
+ const violations = [];
187
+ for (const tool of tools) {
188
+ if (tool.name.length > BUDGETS.name) {
189
+ violations.push(`Tool name too long: ${tool.name}`);
190
+ }
191
+ if (tool.description.length > BUDGETS.description) {
192
+ violations.push(`Description too long: ${tool.name}`);
193
+ }
194
+ walkSchemaProperties(tool.inputSchema, (name, node) => {
195
+ if (name.length > BUDGETS.paramName) {
196
+ violations.push(`Param name too long on ${tool.name}: ${name}`);
197
+ }
198
+ const description = node.description;
199
+ if (typeof description === "string" && description.length > BUDGETS.paramDescription) {
200
+ violations.push(`Param description too long on ${tool.name}.${name}`);
201
+ }
202
+ });
203
+ }
204
+ return violations;
205
+ }
206
+ function runToolSelectionSmokeTest(tools, prompt, expected) {
207
+ const haystack = `${prompt} ${tools.map((t) => t.description).join(" ")}`.toLowerCase();
208
+ return haystack.includes(expected.toLowerCase()) || tools.some((t) => t.name === expected);
209
+ }
210
+ async function runTimelineComparativeProof(page, options) {
211
+ const { runComparativeProof: runComparativeProof2 } = await import("./comparative-proof-QUN7C3KM.js");
212
+ const { invokeWebMcpTool: invokeWebMcpTool2 } = await import("./browser-helpers-RSQLRHHZ.js");
213
+ const proof = await runComparativeProof2(page, {
214
+ buttonLabel: options.buttonLabel,
215
+ actionId: options.actionId
216
+ });
217
+ const timeline = await invokeWebMcpTool2(
218
+ page,
219
+ options.timelineTool ?? "get_troubleshooting_timeline",
220
+ {}
221
+ );
222
+ return {
223
+ domBlockerCount: proof.domBlockerCount,
224
+ timelineEventCount: timeline.events?.length ?? 0
225
+ };
226
+ }
227
+ export {
228
+ MODEL_CONTEXT_MOCK_INIT_SCRIPT,
229
+ MockModelContext,
230
+ assertToolSchemaBudgets,
231
+ collectDisabledActionReasonsFromDom,
232
+ createToolCallEvalFixture,
233
+ diagnoseCheckoutBlockerFromDomOnly,
234
+ diagnoseCheckoutBlockerFromWebMcp,
235
+ executeWebMcpTool,
236
+ expectWebMcpTool,
237
+ inspectDisabledActionFromDom,
238
+ installModelContextMock,
239
+ invokeWebMcpTool,
240
+ listWebMcpTools,
241
+ runComparativeProof,
242
+ runTimelineComparativeProof,
243
+ runToolSelectionSmokeTest,
244
+ snapshotRegisteredTools
245
+ };
@@ -0,0 +1,3 @@
1
+ import { MockModelContext } from './mock-model-context';
2
+ export declare function installModelContextMock(target?: 'document' | 'navigator'): MockModelContext;
3
+ //# sourceMappingURL=install-model-context-mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-model-context-mock.d.ts","sourceRoot":"","sources":["../src/install-model-context-mock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,wBAAgB,uBAAuB,CACrC,MAAM,GAAE,UAAU,GAAG,WAAwB,GAC5C,gBAAgB,CAUlB"}
@@ -0,0 +1,10 @@
1
+ import { MockModelContext } from './mock-model-context';
2
+ export function installModelContextMock(target = 'document') {
3
+ const mock = new MockModelContext();
4
+ const host = target === 'document' ? document : navigator;
5
+ Object.defineProperty(host, 'modelContext', {
6
+ configurable: true,
7
+ value: mock,
8
+ });
9
+ return mock;
10
+ }
@@ -0,0 +1,14 @@
1
+ import type { BrowserModelContextTestingExtensions, BrowserWebMcpToolDescriptor, WebMcpRegisterToolOptions } from '@tooluminati/core';
2
+ export declare class MockModelContext extends EventTarget implements BrowserModelContextTestingExtensions {
3
+ readonly tools: Map<string, BrowserWebMcpToolDescriptor>;
4
+ readonly invocations: Array<{
5
+ name: string;
6
+ args: unknown;
7
+ result?: unknown;
8
+ error?: unknown;
9
+ }>;
10
+ registerTool(tool: BrowserWebMcpToolDescriptor, options?: WebMcpRegisterToolOptions): void;
11
+ getTools(): Promise<BrowserWebMcpToolDescriptor[]>;
12
+ executeTool(toolOrName: unknown, argsJson?: string): Promise<unknown>;
13
+ }
14
+ //# sourceMappingURL=mock-model-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-model-context.d.ts","sourceRoot":"","sources":["../src/mock-model-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oCAAoC,EACpC,2BAA2B,EAC3B,yBAAyB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,gBACX,SAAQ,WACR,YAAW,oCAAoC;IAE/C,QAAQ,CAAC,KAAK,2CAAkD;IAChE,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;QACd,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC,CAAM;IAER,YAAY,CACV,IAAI,EAAE,2BAA2B,EACjC,OAAO,GAAE,yBAA8B,GACtC,IAAI;IAsBD,QAAQ,IAAI,OAAO,CAAC,2BAA2B,EAAE,CAAC;IAIlD,WAAW,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,SAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CA0B1E"}
@@ -0,0 +1,45 @@
1
+ export class MockModelContext extends EventTarget {
2
+ tools = new Map();
3
+ invocations = [];
4
+ registerTool(tool, options = {}) {
5
+ if (this.tools.has(tool.name)) {
6
+ throw new Error(`Tool already registered: ${tool.name}`);
7
+ }
8
+ if (options.signal?.aborted) {
9
+ return;
10
+ }
11
+ this.tools.set(tool.name, tool);
12
+ this.dispatchEvent(new Event('toolchange'));
13
+ options.signal?.addEventListener('abort', () => {
14
+ this.tools.delete(tool.name);
15
+ this.dispatchEvent(new Event('toolchange'));
16
+ }, { once: true });
17
+ }
18
+ async getTools() {
19
+ return [...this.tools.values()];
20
+ }
21
+ async executeTool(toolOrName, argsJson = '{}') {
22
+ const name = typeof toolOrName === 'string'
23
+ ? toolOrName
24
+ : toolOrName.name;
25
+ if (!name) {
26
+ throw new Error('Tool name is required.');
27
+ }
28
+ const tool = this.tools.get(name);
29
+ if (!tool) {
30
+ throw new Error(`Tool not found: ${name}`);
31
+ }
32
+ const args = JSON.parse(argsJson);
33
+ try {
34
+ const result = await tool.execute(args, {
35
+ signal: new AbortController().signal,
36
+ });
37
+ this.invocations.push({ name, args, result });
38
+ return result;
39
+ }
40
+ catch (error) {
41
+ this.invocations.push({ name, args, error });
42
+ throw error;
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,2 @@
1
+ export declare const MODEL_CONTEXT_MOCK_INIT_SCRIPT = "\n(() => {\n const tools = new Map();\n\n Object.defineProperty(document, 'modelContext', {\n configurable: true,\n value: {\n registerTool(tool, options = {}) {\n if (options.signal?.aborted) {\n return;\n }\n\n tools.set(tool.name, tool);\n options.signal?.addEventListener(\n 'abort',\n () => {\n tools.delete(tool.name);\n },\n { once: true },\n );\n },\n async getTools() {\n return [...tools.values()];\n },\n async executeTool(toolOrName, argsJson = '{}') {\n const name =\n typeof toolOrName === 'string'\n ? toolOrName\n : toolOrName?.name;\n const tool = name ? tools.get(name) : undefined;\n if (!tool) {\n throw new Error('Tool not found: ' + name);\n }\n\n return tool.execute(JSON.parse(argsJson));\n },\n },\n });\n})();\n";
2
+ //# sourceMappingURL=model-context-mock-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-context-mock-script.d.ts","sourceRoot":"","sources":["../src/model-context-mock-script.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,8BAA8B,67BAuC1C,CAAC"}
@@ -0,0 +1,40 @@
1
+ export const MODEL_CONTEXT_MOCK_INIT_SCRIPT = `
2
+ (() => {
3
+ const tools = new Map();
4
+
5
+ Object.defineProperty(document, 'modelContext', {
6
+ configurable: true,
7
+ value: {
8
+ registerTool(tool, options = {}) {
9
+ if (options.signal?.aborted) {
10
+ return;
11
+ }
12
+
13
+ tools.set(tool.name, tool);
14
+ options.signal?.addEventListener(
15
+ 'abort',
16
+ () => {
17
+ tools.delete(tool.name);
18
+ },
19
+ { once: true },
20
+ );
21
+ },
22
+ async getTools() {
23
+ return [...tools.values()];
24
+ },
25
+ async executeTool(toolOrName, argsJson = '{}') {
26
+ const name =
27
+ typeof toolOrName === 'string'
28
+ ? toolOrName
29
+ : toolOrName?.name;
30
+ const tool = name ? tools.get(name) : undefined;
31
+ if (!tool) {
32
+ throw new Error('Tool not found: ' + name);
33
+ }
34
+
35
+ return tool.execute(JSON.parse(argsJson));
36
+ },
37
+ },
38
+ });
39
+ })();
40
+ `;
@@ -0,0 +1,32 @@
1
+ export interface ToolSchemaSnapshot {
2
+ name: string;
3
+ description: string;
4
+ inputSchema?: unknown;
5
+ annotations?: Record<string, unknown> | undefined;
6
+ source?: string;
7
+ }
8
+ export interface ToolCallEvalCase {
9
+ prompt: string;
10
+ expectedTool: string;
11
+ args?: Record<string, unknown>;
12
+ }
13
+ export interface ToolCallEvalFixture {
14
+ tools: ToolSchemaSnapshot[];
15
+ cases: ToolCallEvalCase[];
16
+ }
17
+ export declare function snapshotRegisteredTools(page: {
18
+ evaluate: <T>(fn: () => T) => Promise<T>;
19
+ }): Promise<ToolSchemaSnapshot[]>;
20
+ export declare function createToolCallEvalFixture(tools: ToolSchemaSnapshot[], cases: ToolCallEvalCase[]): ToolCallEvalFixture;
21
+ export declare function assertToolSchemaBudgets(tools: ToolSchemaSnapshot[]): string[];
22
+ /** Lightweight fixture helper; does not invoke a model or browser agent. */
23
+ export declare function runToolSelectionSmokeTest(tools: ToolSchemaSnapshot[], prompt: string, expected: string): boolean;
24
+ export declare function runTimelineComparativeProof(page: Parameters<typeof import('./comparative-proof').runComparativeProof>[0], options: {
25
+ buttonLabel: string;
26
+ actionId: string;
27
+ timelineTool?: string;
28
+ }): Promise<{
29
+ domBlockerCount: number;
30
+ timelineEventCount: number;
31
+ }>;
32
+ //# sourceMappingURL=webmcp-evals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-evals.d.ts","sourceRoot":"","sources":["../src/webmcp-evals.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AA4BD,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE;IAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,GACjD,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAuB/B;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,EAAE,EAC3B,KAAK,EAAE,gBAAgB,EAAE,GACxB,mBAAmB,CAErB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,MAAM,EAAE,CAuB7E;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,EAAE,EAC3B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAMT;AAED,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,UAAU,CAAC,cAAc,qBAAqB,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,EAC7E,OAAO,EAAE;IACP,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,OAAO,CAAC;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC,CAgBlE"}
@@ -0,0 +1,81 @@
1
+ const BUDGETS = {
2
+ name: 30,
3
+ paramName: 30,
4
+ paramDescription: 150,
5
+ description: 500,
6
+ output: 1500,
7
+ };
8
+ function walkSchemaProperties(schema, visit) {
9
+ if (!schema || typeof schema !== 'object') {
10
+ return;
11
+ }
12
+ const node = schema;
13
+ const properties = node.properties;
14
+ if (properties && typeof properties === 'object') {
15
+ for (const [name, child] of Object.entries(properties)) {
16
+ if (child && typeof child === 'object') {
17
+ visit(name, child);
18
+ }
19
+ }
20
+ }
21
+ }
22
+ export async function snapshotRegisteredTools(page) {
23
+ return page.evaluate(() => {
24
+ const context = document.modelContext;
25
+ if (!context?.getTools) {
26
+ return [];
27
+ }
28
+ return context.getTools().then((tools) => tools.map((tool) => ({
29
+ name: String(tool.name ?? ''),
30
+ description: String(tool.description ?? ''),
31
+ inputSchema: tool.inputSchema,
32
+ ...(tool.annotations
33
+ ? { annotations: tool.annotations }
34
+ : {}),
35
+ })));
36
+ });
37
+ }
38
+ export function createToolCallEvalFixture(tools, cases) {
39
+ return { tools, cases };
40
+ }
41
+ export function assertToolSchemaBudgets(tools) {
42
+ const violations = [];
43
+ for (const tool of tools) {
44
+ if (tool.name.length > BUDGETS.name) {
45
+ violations.push(`Tool name too long: ${tool.name}`);
46
+ }
47
+ if (tool.description.length > BUDGETS.description) {
48
+ violations.push(`Description too long: ${tool.name}`);
49
+ }
50
+ walkSchemaProperties(tool.inputSchema, (name, node) => {
51
+ if (name.length > BUDGETS.paramName) {
52
+ violations.push(`Param name too long on ${tool.name}: ${name}`);
53
+ }
54
+ const description = node.description;
55
+ if (typeof description === 'string' &&
56
+ description.length > BUDGETS.paramDescription) {
57
+ violations.push(`Param description too long on ${tool.name}.${name}`);
58
+ }
59
+ });
60
+ }
61
+ return violations;
62
+ }
63
+ /** Lightweight fixture helper; does not invoke a model or browser agent. */
64
+ export function runToolSelectionSmokeTest(tools, prompt, expected) {
65
+ const haystack = `${prompt} ${tools.map((t) => t.description).join(' ')}`.toLowerCase();
66
+ return (haystack.includes(expected.toLowerCase()) ||
67
+ tools.some((t) => t.name === expected));
68
+ }
69
+ export async function runTimelineComparativeProof(page, options) {
70
+ const { runComparativeProof } = await import('./comparative-proof');
71
+ const { invokeWebMcpTool } = await import('./browser-helpers');
72
+ const proof = await runComparativeProof(page, {
73
+ buttonLabel: options.buttonLabel,
74
+ actionId: options.actionId,
75
+ });
76
+ const timeline = await invokeWebMcpTool(page, options.timelineTool ?? 'get_troubleshooting_timeline', {});
77
+ return {
78
+ domBlockerCount: proof.domBlockerCount,
79
+ timelineEventCount: timeline.events?.length ?? 0,
80
+ };
81
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@tooluminati/testing",
3
+ "version": "0.1.0",
4
+ "description": "Testing utilities for Tooluminati.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "@tooluminati/core": "0.1.0"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "license": "MIT",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/jitterbox/Tooluminati.git",
30
+ "directory": "packages/testing"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/jitterbox/Tooluminati/issues"
34
+ },
35
+ "homepage": "https://github.com/jitterbox/Tooluminati#readme",
36
+ "keywords": [
37
+ "tooluminati",
38
+ "webmcp",
39
+ "react",
40
+ "testing",
41
+ "playwright"
42
+ ],
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts --format esm,cjs && tsc -p tsconfig.json --emitDeclarationOnly",
48
+ "typecheck": "tsc --noEmit"
49
+ }
50
+ }