@yasserkhanorg/e2e-agents 0.5.12 → 0.5.14

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ai_mapping.d.ts","sourceRoot":"","sources":["../../src/agent/ai_mapping.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAC;AA4BvD,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAuTD,wBAAsB,iBAAiB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,QAAQ,EAAE,GAClB,OAAO,CAAC,eAAe,CAAC,CA4O1B"}
1
+ {"version":3,"file":"ai_mapping.d.ts","sourceRoot":"","sources":["../../src/agent/ai_mapping.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAC;AA4BvD,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AA4WD,wBAAsB,iBAAiB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,QAAQ,EAAE,GAClB,OAAO,CAAC,eAAe,CAAC,CA2O1B"}
@@ -139,6 +139,21 @@ function isStrongCandidateMatch(flow, matchedKeywords) {
139
139
  const keywords = flowKeywords(flow);
140
140
  return keywords.length === 1 && matchedKeywords[0].length >= MIN_SINGLE_KEYWORD_LENGTH;
141
141
  }
142
+ // Stop-words excluded from content-fallback keyword matching.
143
+ const CONTENT_FALLBACK_STOP_WORDS = new Set(['and', 'for', 'the', 'to', 'of', 'on', 'at', 'with', 'in', 'a', 'an']);
144
+ // Returns raw (unfiltered) tokens for flows where flowKeywords() returns nothing.
145
+ // Used exclusively for content-title matching when all standard keywords are low-signal.
146
+ // Empty array is returned when the flow already has effective path keywords.
147
+ function contentFallbackKeywords(flow) {
148
+ if (flowKeywords(flow).length > 0) {
149
+ return [];
150
+ }
151
+ return (0, utils_js_1.uniqueTokens)([
152
+ ...(0, utils_js_1.tokenize)(flow.id || ''),
153
+ ...(0, utils_js_1.tokenize)(flow.name || ''),
154
+ ...(flow.keywords || []),
155
+ ]).filter((k) => k.length >= 3 && !CONTENT_FALLBACK_STOP_WORDS.has(k));
156
+ }
142
157
  // Extract test/describe/it title strings from file content for semantic matching.
143
158
  function extractTestTitles(content) {
144
159
  const titles = [];
@@ -214,7 +229,42 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
214
229
  }
215
230
  contentCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
216
231
  }
217
- const allCandidates = [...strongCandidates, ...contentCandidates.slice(0, perFlowLimit)];
232
+ // Pass 2b: comprehensive fallback for all-low-signal flows.
233
+ // When flowKeywords() is empty (all tokens are low-signal), flowKeywords-based
234
+ // matching in both Pass 1 and Pass 2 yields nothing. As a last resort, search
235
+ // test titles using the raw unfiltered tokens from the flow ID/name, but require
236
+ // ALL tokens to match simultaneously — they are individually weak signals so the
237
+ // full conjunction is needed to establish behavioral coverage evidence.
238
+ const fallbackCandidates = [];
239
+ if (strongCandidates.length === 0 && contentCandidates.length === 0) {
240
+ const fallbackKws = contentFallbackKeywords(flow);
241
+ if (fallbackKws.length > 0) {
242
+ for (const testPath of normalizedTests) {
243
+ const testFile = testByNormalizedPath.get(testPath);
244
+ if (!testFile?.content) {
245
+ continue;
246
+ }
247
+ const haystack = extractTestTitles(testFile.content).toLowerCase();
248
+ if (!haystack) {
249
+ continue;
250
+ }
251
+ const matched = fallbackKws.filter((k) => haystack.includes(k));
252
+ // For 3+ tokens, n-1 must match (allows one absent word like "view");
253
+ // for 1-2 tokens all must match.
254
+ const required = fallbackKws.length >= 3 ? fallbackKws.length - 1 : fallbackKws.length;
255
+ if (matched.length < required) {
256
+ continue;
257
+ }
258
+ fallbackCandidates.push({ path: testPath, score: matched.length, matchedKeywords: matched });
259
+ }
260
+ fallbackCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
261
+ }
262
+ }
263
+ const allCandidates = [
264
+ ...strongCandidates,
265
+ ...contentCandidates.slice(0, perFlowLimit),
266
+ ...fallbackCandidates.slice(0, perFlowLimit),
267
+ ];
218
268
  if (allCandidates.length === 0) {
219
269
  // Exact-name fallback: if the flow ID has no effective keywords (all tokens are
220
270
  // low-signal, e.g. view_user_group_modal), look for a test whose path contains
@@ -371,12 +421,11 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
371
421
  'Rules:',
372
422
  '- Keep at most 5 tests per flow.',
373
423
  '- Use exact flowId values from FLOWS.',
374
- '- Only map a test when its path clearly matches the flow scenario. Generic subsystem similarity is not enough.',
424
+ '- Map a test when you have behavioral evidence it covers the flow scenario, from the file path OR from test titles in the content. A file named search_user_post_spec.js with titles like "search for message by keyword" covers search_messages. Generic subsystem similarity without behavioral evidence is not enough.',
375
425
  '- A flow may only map to tests listed under FLOW_CANDIDATE_SIGNALS for that flow.',
376
- '- Treat single-keyword or broad subsystem overlap as insufficient evidence.',
377
- '- If the candidate path overlap is weak or ambiguous, return tests: [].',
378
- '- If unsure for a flow, return tests: [].',
379
- '- For EVERY flow (whether or not tests were found), return missingScenarios with 3-5 key user-facing test scenarios that must be covered. Write each as a short imperative statement starting with a verb (e.g. "Search for a message by keyword and verify results appear"). For mapped flows, focus on what the existing tests do NOT cover; for unmapped flows, describe the core scenarios a new test should include.',
426
+ '- When a flow has 3 or more candidate tests that collectively cover its behavioral domain (e.g. multiple search specs for search_messages, multiple messaging specs for view_post_in_channel), map all of them. Collective coverage across multiple files counts as full coverage.',
427
+ '- Only return tests: [] when NO candidate file has clear behavioral overlap to the flow.',
428
+ '- missingScenarios: for flows with no test mappings (tests: []), list 3-5 core scenarios that a new test must cover. For flows WITH test mappings, ONLY list scenarios that are genuinely absent from ALL mapped tests combined — if the mapped tests collectively cover the core user-facing scenarios well, return missingScenarios: []. Do not invent edge-case gaps for well-covered flows.',
380
429
  '',
381
430
  `FLOWS (${prioritizedFlows.length}):`,
382
431
  JSON.stringify(prioritizedFlows.map((flow) => ({
@@ -136,6 +136,21 @@ function isStrongCandidateMatch(flow, matchedKeywords) {
136
136
  const keywords = flowKeywords(flow);
137
137
  return keywords.length === 1 && matchedKeywords[0].length >= MIN_SINGLE_KEYWORD_LENGTH;
138
138
  }
139
+ // Stop-words excluded from content-fallback keyword matching.
140
+ const CONTENT_FALLBACK_STOP_WORDS = new Set(['and', 'for', 'the', 'to', 'of', 'on', 'at', 'with', 'in', 'a', 'an']);
141
+ // Returns raw (unfiltered) tokens for flows where flowKeywords() returns nothing.
142
+ // Used exclusively for content-title matching when all standard keywords are low-signal.
143
+ // Empty array is returned when the flow already has effective path keywords.
144
+ function contentFallbackKeywords(flow) {
145
+ if (flowKeywords(flow).length > 0) {
146
+ return [];
147
+ }
148
+ return uniqueTokens([
149
+ ...tokenize(flow.id || ''),
150
+ ...tokenize(flow.name || ''),
151
+ ...(flow.keywords || []),
152
+ ]).filter((k) => k.length >= 3 && !CONTENT_FALLBACK_STOP_WORDS.has(k));
153
+ }
139
154
  // Extract test/describe/it title strings from file content for semantic matching.
140
155
  function extractTestTitles(content) {
141
156
  const titles = [];
@@ -211,7 +226,42 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
211
226
  }
212
227
  contentCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
213
228
  }
214
- const allCandidates = [...strongCandidates, ...contentCandidates.slice(0, perFlowLimit)];
229
+ // Pass 2b: comprehensive fallback for all-low-signal flows.
230
+ // When flowKeywords() is empty (all tokens are low-signal), flowKeywords-based
231
+ // matching in both Pass 1 and Pass 2 yields nothing. As a last resort, search
232
+ // test titles using the raw unfiltered tokens from the flow ID/name, but require
233
+ // ALL tokens to match simultaneously — they are individually weak signals so the
234
+ // full conjunction is needed to establish behavioral coverage evidence.
235
+ const fallbackCandidates = [];
236
+ if (strongCandidates.length === 0 && contentCandidates.length === 0) {
237
+ const fallbackKws = contentFallbackKeywords(flow);
238
+ if (fallbackKws.length > 0) {
239
+ for (const testPath of normalizedTests) {
240
+ const testFile = testByNormalizedPath.get(testPath);
241
+ if (!testFile?.content) {
242
+ continue;
243
+ }
244
+ const haystack = extractTestTitles(testFile.content).toLowerCase();
245
+ if (!haystack) {
246
+ continue;
247
+ }
248
+ const matched = fallbackKws.filter((k) => haystack.includes(k));
249
+ // For 3+ tokens, n-1 must match (allows one absent word like "view");
250
+ // for 1-2 tokens all must match.
251
+ const required = fallbackKws.length >= 3 ? fallbackKws.length - 1 : fallbackKws.length;
252
+ if (matched.length < required) {
253
+ continue;
254
+ }
255
+ fallbackCandidates.push({ path: testPath, score: matched.length, matchedKeywords: matched });
256
+ }
257
+ fallbackCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
258
+ }
259
+ }
260
+ const allCandidates = [
261
+ ...strongCandidates,
262
+ ...contentCandidates.slice(0, perFlowLimit),
263
+ ...fallbackCandidates.slice(0, perFlowLimit),
264
+ ];
215
265
  if (allCandidates.length === 0) {
216
266
  // Exact-name fallback: if the flow ID has no effective keywords (all tokens are
217
267
  // low-signal, e.g. view_user_group_modal), look for a test whose path contains
@@ -368,12 +418,11 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
368
418
  'Rules:',
369
419
  '- Keep at most 5 tests per flow.',
370
420
  '- Use exact flowId values from FLOWS.',
371
- '- Only map a test when its path clearly matches the flow scenario. Generic subsystem similarity is not enough.',
421
+ '- Map a test when you have behavioral evidence it covers the flow scenario, from the file path OR from test titles in the content. A file named search_user_post_spec.js with titles like "search for message by keyword" covers search_messages. Generic subsystem similarity without behavioral evidence is not enough.',
372
422
  '- A flow may only map to tests listed under FLOW_CANDIDATE_SIGNALS for that flow.',
373
- '- Treat single-keyword or broad subsystem overlap as insufficient evidence.',
374
- '- If the candidate path overlap is weak or ambiguous, return tests: [].',
375
- '- If unsure for a flow, return tests: [].',
376
- '- For EVERY flow (whether or not tests were found), return missingScenarios with 3-5 key user-facing test scenarios that must be covered. Write each as a short imperative statement starting with a verb (e.g. "Search for a message by keyword and verify results appear"). For mapped flows, focus on what the existing tests do NOT cover; for unmapped flows, describe the core scenarios a new test should include.',
423
+ '- When a flow has 3 or more candidate tests that collectively cover its behavioral domain (e.g. multiple search specs for search_messages, multiple messaging specs for view_post_in_channel), map all of them. Collective coverage across multiple files counts as full coverage.',
424
+ '- Only return tests: [] when NO candidate file has clear behavioral overlap to the flow.',
425
+ '- missingScenarios: for flows with no test mappings (tests: []), list 3-5 core scenarios that a new test must cover. For flows WITH test mappings, ONLY list scenarios that are genuinely absent from ALL mapped tests combined — if the mapped tests collectively cover the core user-facing scenarios well, return missingScenarios: []. Do not invent edge-case gaps for well-covered flows.',
377
426
  '',
378
427
  `FLOWS (${prioritizedFlows.length}):`,
379
428
  JSON.stringify(prioritizedFlows.map((flow) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
4
4
  "description": "Pluggable LLM provider library for AI-powered test automation. Use Claude, Ollama, or your own LLM. Integrate with Playwright, Jest, or any test framework. MCP server for test agents, cost tracking, and hybrid provider mode.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",