@sudobility/testomniac_runner_service 0.1.89 → 0.1.91

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.
@@ -172,6 +172,14 @@ export declare class PageAnalyzer {
172
172
  private hasDisabledAppearanceSignal;
173
173
  private describeActionableItem;
174
174
  private semanticText;
175
+ /**
176
+ * Max representatives per action style. Product grids can have dozens of
177
+ * cards each producing a unique container fingerprint (because the product
178
+ * title differs), but the CTA buttons ("ADD TO CART", "Select Options") are
179
+ * functionally identical. Capping per style prevents 14× duplicate hover
180
+ * tests for the same button type across different cards.
181
+ */
182
+ private static readonly MAX_REPS_PER_STYLE;
175
183
  private selectRepresentativeItems;
176
184
  private representativeActionStyle;
177
185
  private pickRepresentativeItem;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyzer/page-analyzer/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyzer/page-analyzer/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { PlaywrightAction, ExpectationType, ExpectationSeverity, } from "@sudobility/testomniac_types";
2
2
  import { buildReplaySelectorFromActionableItem, matchesActionableItemSelector, } from "../../browser/replay-selector";
3
- import { computeHashes, computeActionableHash, sha256, normalizeHtml, } from "../../browser/page-utils";
3
+ import { computeHashes, computeActionableHash, sha256, normalizeHtml, htmlToMarkdown, } from "../../browser/page-utils";
4
4
  import { getBody, getContentBody } from "../../scanner/html-decomposer";
5
5
  import { createHash } from "node:crypto";
6
6
  import { fillValuePlanner } from "../../planners/fill-value-planner";
@@ -260,6 +260,32 @@ export class PageAnalyzer {
260
260
  return;
261
261
  }
262
262
  const normalizedContext = this.normalizeContext(context);
263
+ const isHover = this.isHoverOnly(testInteraction);
264
+ const currentPath = normalizedContext.currentPath.trim();
265
+ // For non-hover interactions: check cheap path/hash guards BEFORE the
266
+ // expensive ensureTargetPageState call so we can skip all the API work
267
+ // (scaffold resolution, hash computation, page-state creation) when the
268
+ // page has already been covered in this run.
269
+ if (!isHover) {
270
+ if (this.generatedPaths.has(currentPath)) {
271
+ logAnalyzer("generate:page-already-covered", {
272
+ sourceTitle: testInteraction.title,
273
+ currentTestInteractionId: normalizedContext.currentTestInteractionId,
274
+ currentPath,
275
+ });
276
+ return;
277
+ }
278
+ const actionableHash = await computeActionableHash(normalizedContext.actionableItems);
279
+ if (this.generatedActionableHashes.has(actionableHash)) {
280
+ logAnalyzer("generate:actionable-items-already-covered", {
281
+ sourceTitle: testInteraction.title,
282
+ currentTestInteractionId: normalizedContext.currentTestInteractionId,
283
+ currentPath,
284
+ actionableHash,
285
+ });
286
+ return;
287
+ }
288
+ }
263
289
  const currentPageStateId = await this.ensureTargetPageState(normalizedContext);
264
290
  const resolvedContext = {
265
291
  ...normalizedContext,
@@ -278,7 +304,7 @@ export class PageAnalyzer {
278
304
  // Hover-only interactions have their own same-page-state handling inside
279
305
  // generateHoverFollowUpCases (including click-follow-up reconciliation),
280
306
  // so let them through to that dedicated path.
281
- if (this.isHoverOnly(testInteraction)) {
307
+ if (isHover) {
282
308
  logAnalyzer("generate:hover-only", {
283
309
  sourceTitle: testInteraction.title,
284
310
  currentTestInteractionId: resolvedContext.currentTestInteractionId,
@@ -300,34 +326,9 @@ export class PageAnalyzer {
300
326
  });
301
327
  return;
302
328
  }
303
- // Skip generation if we already generated tests for this path during this
304
- // run. The check is scoped to the PageAnalyzer instance (one per run) so
305
- // previous runs on the same runner don't block re-discovery.
306
- const currentPath = resolvedContext.currentPath.trim();
307
- if (this.generatedPaths.has(currentPath)) {
308
- logAnalyzer("generate:page-already-covered", {
309
- sourceTitle: testInteraction.title,
310
- currentTestInteractionId: resolvedContext.currentTestInteractionId,
311
- currentPageStateId: resolvedContext.currentPageStateId,
312
- currentPath,
313
- });
314
- return;
315
- }
316
- // Skip generation if a different path already produced the same set of
317
- // interactive elements. For example /store/ and /store/all-items/ may
318
- // resolve to the same product grid — generating hover/click tests for
319
- // both is redundant.
329
+ // Re-check actionableHash (already passed the guard above, but we need
330
+ // to record it and the path now that we're committed to generation).
320
331
  const actionableHash = await computeActionableHash(resolvedContext.actionableItems);
321
- if (this.generatedActionableHashes.has(actionableHash)) {
322
- logAnalyzer("generate:actionable-items-already-covered", {
323
- sourceTitle: testInteraction.title,
324
- currentTestInteractionId: resolvedContext.currentTestInteractionId,
325
- currentPageStateId: resolvedContext.currentPageStateId,
326
- currentPath,
327
- actionableHash,
328
- });
329
- return;
330
- }
331
332
  this.generatedPaths.add(currentPath);
332
333
  this.generatedActionableHashes.add(actionableHash);
333
334
  // If a hover+click navigated to a new page, create a direct navigation
@@ -714,7 +715,7 @@ export class PageAnalyzer {
714
715
  pageId: context.pageId,
715
716
  sizeClass: context.sizeClass,
716
717
  hashes,
717
- contentText: context.html.slice(0, 5000),
718
+ contentText: htmlToMarkdown(context.html).slice(0, 5000),
718
719
  contentHtmlElementId: contentElement.id,
719
720
  screenshotPath: context.screenshotPath,
720
721
  });
@@ -2384,6 +2385,14 @@ export class PageAnalyzer {
2384
2385
  .join(" ")
2385
2386
  .toLowerCase();
2386
2387
  }
2388
+ /**
2389
+ * Max representatives per action style. Product grids can have dozens of
2390
+ * cards each producing a unique container fingerprint (because the product
2391
+ * title differs), but the CTA buttons ("ADD TO CART", "Select Options") are
2392
+ * functionally identical. Capping per style prevents 14× duplicate hover
2393
+ * tests for the same button type across different cards.
2394
+ */
2395
+ static MAX_REPS_PER_STYLE = 2;
2387
2396
  selectRepresentativeItems(items) {
2388
2397
  const groups = new Map();
2389
2398
  const passthrough = [];
@@ -2399,7 +2408,31 @@ export class PageAnalyzer {
2399
2408
  groups.set(key, bucket);
2400
2409
  }
2401
2410
  const representatives = Array.from(groups.values()).map(group => this.pickRepresentativeItem(group));
2402
- return [...passthrough, ...representatives];
2411
+ // Cap per action style: when many different containers share the same
2412
+ // functional action (e.g. 14 product cards each with "ADD TO CART"),
2413
+ // keep at most MAX_REPS_PER_STYLE representatives per style.
2414
+ const byStyle = new Map();
2415
+ for (const rep of representatives) {
2416
+ const style = this.representativeActionStyle(rep);
2417
+ const bucket = byStyle.get(style) ?? [];
2418
+ bucket.push(rep);
2419
+ byStyle.set(style, bucket);
2420
+ }
2421
+ const capped = Array.from(byStyle.values()).flatMap(group => group.length <= PageAnalyzer.MAX_REPS_PER_STYLE
2422
+ ? group
2423
+ : group.slice(0, PageAnalyzer.MAX_REPS_PER_STYLE));
2424
+ // Also cap passthrough items (no container fingerprint) by action style
2425
+ const passthroughByStyle = new Map();
2426
+ for (const item of passthrough) {
2427
+ const style = this.representativeActionStyle(item);
2428
+ const bucket = passthroughByStyle.get(style) ?? [];
2429
+ bucket.push(item);
2430
+ passthroughByStyle.set(style, bucket);
2431
+ }
2432
+ const cappedPassthrough = Array.from(passthroughByStyle.values()).flatMap(group => group.length <= PageAnalyzer.MAX_REPS_PER_STYLE
2433
+ ? group
2434
+ : group.slice(0, PageAnalyzer.MAX_REPS_PER_STYLE));
2435
+ return [...cappedPassthrough, ...capped];
2403
2436
  }
2404
2437
  representativeActionStyle(item) {
2405
2438
  const text = this.semanticText(item);