@molecule/api-ai-tools 1.10.1 → 1.10.3

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.
@@ -203,26 +203,142 @@ function messageOf(value) {
203
203
  const FILLING_ACTION = /\\.(?:fill|type|pressSequentially|selectOption)$/
204
204
 
205
205
  // The text a failing assertion waited for, named by its own error: a \`has-text(…)\` CSS
206
- // pseudo-class, a \`getByText(…)\` locator, or a \`toContainText(…)\` expectation. A strict-mode
207
- // violation is not this shape — two elements DID match the text — and neither is a wait on a
208
- // selector with no text argument. The ANSI colouring Playwright leaves in the message is dropped
209
- // before the read, or the quotes it wraps around a locator would never parse.
206
+ // pseudo-class, a \`getByText(…)\` locator, or a \`toContainText(…)\` expectation — and only when
207
+ // the failure IS an absence of that text. A strict-mode violation is not this shape (two elements
208
+ // DID match), and neither is a wait on a selector with no text argument.
209
+ //
210
+ // A locator that RESOLVED is not an absent element either: Playwright names the element it matched
211
+ // and then says why the action could not run (m194's disabled button carried the very title the
212
+ // note called missing). A disabled, invisible, unstable, detached or intercepted control is a
213
+ // control problem, not a rendering one, so none of those shapes may produce a missing-text reading.
214
+ // The ANSI colouring Playwright leaves in the message is dropped before the read, or the quotes it
215
+ // wraps around a locator would never parse.
210
216
  function waitedTextOf(message) {
211
217
  if (!message) return null
212
218
  const text = String(message).replace(/\\u001b\\[[0-9;]*m/g, '')
213
219
  if (text.indexOf('strict mode violation') !== -1) return null
220
+ if (text.indexOf('locator resolved to') !== -1) return null
221
+ if (/element is not (?:enabled|visible|stable|attached)/.test(text)) return null
222
+ if (/intercepts? pointer events/.test(text)) return null
223
+ // Playwright's own not-found marker is what an absence assertion leaves behind; a text
224
+ // expectation that failed on an element it DID find is the other absence shape (its received
225
+ // text lacks the expected one).
226
+ const notFound = /element\\(s\\) not found|element is not found/.test(text)
214
227
  const quoted = function (pattern) {
215
228
  const m = pattern.exec(text)
216
229
  return m === null ? null : m[2]
217
230
  }
218
231
  const hasText = quoted(/has-text\\(\\s*(['"])((?:(?!\\1).)*)\\1/)
219
- if (hasText !== null) return hasText
232
+ if (hasText !== null) return notFound ? hasText : null
220
233
  const byText = quoted(/getByText\\(\\s*(['"])((?:(?!\\1).)*)\\1/)
221
- if (byText !== null) return byText
234
+ if (byText !== null) return notFound ? byText : null
222
235
  if (text.indexOf('toContainText') === -1) return null
223
236
  return quoted(/Expected(?: string)?:\\s*(["'])((?:(?!\\1).)*)\\1/)
224
237
  }
225
238
 
239
+ // The attributes of one serialized element's opening tag (\`class="a b" id="c"\`). The trace's
240
+ // snapshots carry them as an object already; the ASSERTION's error prints the tag as text.
241
+ function attrsOf(source) {
242
+ const out = {}
243
+ const pattern = /([A-Za-z_:][-A-Za-z0-9_:.]*)\\s*=\\s*("([^"]*)"|'([^']*)')/g
244
+ let match
245
+ while ((match = pattern.exec(String(source || ''))) !== null)
246
+ out[match[1].toLowerCase()] = match[3] !== undefined ? match[3] : match[4]
247
+ return out
248
+ }
249
+
250
+ // Whether a serialized subtree carries text of its own. A text node is a string; an element is
251
+ // \`[tag, attrs, …children]\`. A reference to an earlier snapshot is not followed here: the caller
252
+ // only makes a claim when every child is readable in place.
253
+ function subtreeText(node) {
254
+ if (typeof node === 'string') return node.trim() !== ''
255
+ if (!Array.isArray(node) || Array.isArray(node[0]) || typeof node[0] !== 'string') return false
256
+ for (let i = 2; i < node.length; i++) if (subtreeText(node[i])) return true
257
+ return false
258
+ }
259
+
260
+ // The serialized element children of a node, in document order (references skipped).
261
+ function elementChildren(node) {
262
+ const out = []
263
+ if (!Array.isArray(node)) return out
264
+ for (let i = 2; i < node.length; i++) {
265
+ const child = node[i]
266
+ if (typeof child === 'string' || !Array.isArray(child)) continue
267
+ if (Array.isArray(child[0]) || typeof child[0] !== 'string') continue
268
+ out.push(child)
269
+ }
270
+ return out
271
+ }
272
+
273
+ // The first element in a serialized DOM tree with this tag and (when given) id or first class.
274
+ // Playwright's snapshot serializes elements as \`[tag, attrs, …children]\`.
275
+ function findElement(node, tag, id, cls) {
276
+ if (typeof node === 'string' || !Array.isArray(node)) return null
277
+ if (Array.isArray(node[0]) || typeof node[0] !== 'string') return null
278
+ if (node[0].toLowerCase() === tag) {
279
+ const attrs = node[1] && typeof node[1] === 'object' ? node[1] : {}
280
+ const idMatch = id === '' || String(attrs.id || '') === id
281
+ const classes = String(attrs.class || attrs.className || '').split(/\\s+/)
282
+ const classMatch = id !== '' || cls === '' || classes.indexOf(cls) !== -1
283
+ if (idMatch && classMatch) return node
284
+ }
285
+ for (let i = 2; i < node.length; i++) {
286
+ const deeper = findElement(node[i], tag, id, cls)
287
+ if (deeper !== null) return deeper
288
+ }
289
+ return null
290
+ }
291
+
292
+ // A locator that RESOLVED whose box Playwright calls hidden. Playwright reports "hidden" for an
293
+ // element with a zero-size box even when it is painted, so the interesting question is why the
294
+ // box is empty — and the trace's own frame snapshot carries the element's children, the only
295
+ // evidence the run would otherwise have to probe for (xr-molecule-flash-m197: \`a.navbar-brand\`
296
+ // was 0x4 px because its only child was an <img> with no CSS size, and the note said only that
297
+ // "the cause is in the page"; the run spent 4.5 minutes rasterising an SVG to PNG).
298
+ // Only what the snapshot proves is reported: an inline display:none/visibility:hidden, a lone
299
+ // <img> child, or a textless element with no children. Anything else says nothing.
300
+ function hiddenBoxOf(message, frames) {
301
+ if (!message) return null
302
+ const text = String(message).replace(/\\u001b\\[[0-9;]*m/g, '')
303
+ if (text.indexOf('strict mode violation') !== -1) return null
304
+ if (!/Received:\\s*hidden/.test(text) && text.indexOf('element is not visible') === -1) return null
305
+ const resolved = /locator resolved to <([A-Za-z][\\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/.exec(text)
306
+ if (resolved === null) return null
307
+ const tag = resolved[1].toLowerCase()
308
+ const attrs = attrsOf(resolved[2])
309
+ const id = attrs.id || ''
310
+ const cls = String(attrs.class || attrs.className || '').split(/\\s+/).filter(Boolean)[0] || ''
311
+ const named = /Locator:\\s*([^\\n]+)/.exec(text)
312
+ const reading = {
313
+ locator: named !== null ? named[1].trim() : cls ? tag + '.' + cls : tag,
314
+ tag: tag,
315
+ }
316
+ const snapshot = frames.length > 0 ? frames[frames.length - 1].snapshot : null
317
+ const node = snapshot === null || snapshot === undefined ? null : findElement(snapshot, tag, id, cls)
318
+ if (node === null) return null
319
+ const own = node[1] && typeof node[1] === 'object' ? node[1] : {}
320
+ if (typeof own.style === 'string' && own.style) reading.style = own.style.slice(0, 160)
321
+ if (!subtreeText(node)) {
322
+ const children = elementChildren(node)
323
+ if (children.length === 1 && children[0][0].toLowerCase() === 'img') {
324
+ const image = children[0][1] && typeof children[0][1] === 'object' ? children[0][1] : {}
325
+ reading.image = {}
326
+ for (const key of ['src', 'width', 'height', 'style', 'alt']) {
327
+ if (typeof image[key] === 'string' && image[key])
328
+ reading.image[key] = image[key].slice(0, 120)
329
+ }
330
+ if (typeof image.class === 'string' && image.class) reading.image.className = image.class.slice(0, 120)
331
+ } else if (children.length === 0) {
332
+ reading.empty = true
333
+ }
334
+ }
335
+ const hiddenStyle = /(?:^|;)\\s*display\\s*:\\s*none|(?:^|;)\\s*visibility\\s*:\\s*hidden/.test(
336
+ typeof own.style === 'string' ? own.style : '',
337
+ )
338
+ if (!reading.image && !reading.empty && !hiddenStyle) return null
339
+ return reading
340
+ }
341
+
226
342
  // A request/response body exactly as Playwright stored it: inline on the value, or in a resource
227
343
  // entry the value names. Bounded, and binary is dropped — a JSON body is what this is for, and a
228
344
  // multipart upload would print bytes.
@@ -479,6 +595,11 @@ for (const one of found.slice(0, MAX_TRACES)) {
479
595
  context: ctx.id,
480
596
  pageId: typeof snap.pageId === 'string' ? snap.pageId : undefined,
481
597
  frameId: typeof snap.frameId === 'string' ? snap.frameId : undefined,
598
+ // The snapshot's own DOM tree, kept by reference (the trace line already holds it). The
599
+ // note reads its text only when it has to prove an awaited value was absent from the page
600
+ // (m194's false "dropped before rendering" lead). Playwright serializes text nodes as
601
+ // strings and elements as [tag, attrs, ...children].
602
+ snapshot: snap.html,
482
603
  })
483
604
  }
484
605
  for (const entry of ctx.trace) {
@@ -776,26 +897,108 @@ for (const one of found.slice(0, MAX_TRACES)) {
776
897
  if (sent) r.sent = sent
777
898
  if (got) r.got = got
778
899
  }
900
+ // The text a frame snapshot SHOWED, so a page that still carried the awaited value is never
901
+ // described as having dropped it. Playwright serializes the DOM as strings (text nodes) and
902
+ // [tag, attrs, ...children] elements, with a subtree reference ([[snapshotIndex, nodeIndex]]) to
903
+ // a node an earlier snapshot of the same frame carried. \`script\`/\`style\` bodies are skipped,
904
+ // as the trace viewer skips them: code is not what the page showed. Null when the snapshot is
905
+ // absent or a reference cannot be resolved — an unreadable snapshot proves nothing.
906
+ function snapshotShowed(frame) {
907
+ const tree = frame.snapshot
908
+ if (tree === undefined || tree === null) return null
909
+ const shown = framesAll.filter(function (f) {
910
+ return f.context === frame.context && f.pageId === frame.pageId
911
+ })
912
+ const nodesOf = function (value) {
913
+ const nodes = []
914
+ const collect = function (n) {
915
+ if (typeof n === 'string') { nodes.push(n); return }
916
+ if (!Array.isArray(n) || typeof n[0] !== 'string') return
917
+ for (let i = 2; i < n.length; i++) collect(n[i])
918
+ nodes.push(n)
919
+ }
920
+ collect(value)
921
+ return nodes
922
+ }
923
+ const out = []
924
+ let visited = 0
925
+ let readable = true
926
+ const visit = function (n) {
927
+ if (++visited > 400000) { readable = false; return }
928
+ if (typeof n === 'string') { out.push(n); return }
929
+ if (!Array.isArray(n)) return
930
+ if (Array.isArray(n[0])) {
931
+ const ref = n[0]
932
+ const at = Array.isArray(ref) && typeof ref[0] === 'number' ? ref[0] : -1
933
+ const node = Array.isArray(ref) && typeof ref[1] === 'number' ? ref[1] : -1
934
+ const earlier = at >= 0 && at < shown.length && shown[at].snapshot != null ? shown[at].snapshot : null
935
+ const nodes = earlier === null ? [] : nodesOf(earlier)
936
+ if (node < 0 || node >= nodes.length) { readable = false; return }
937
+ visit(nodes[node])
938
+ return
939
+ }
940
+ if (typeof n[0] !== 'string') return
941
+ const name = n[0].toUpperCase()
942
+ if (name === 'SCRIPT' || name === 'STYLE') return
943
+ for (let i = 2; i < n.length; i++) visit(n[i])
944
+ }
945
+ visit(tree)
946
+ return readable ? out.join('') : null
947
+ }
779
948
  // The text the failing assertion waited for, and a response the SAME page received that carried
780
949
  // it (m169). The assertion's own error names the text; the network log says whether it ever
781
950
  // reached the browser. Only an answered fetch/xhr can say the API returned it — a 4xx body is an
782
951
  // error message, not data. The most focused response wins (smallest body): an articles list also
783
952
  // carries tag names in its \`tagList\`, while the tags response is the collection whose membership
784
953
  // the assertion tests.
954
+ //
955
+ // The "page drops it before rendering" claim is made only when the trace PROVES it: a successful
956
+ // GET of the collection that STARTED AFTER the last navigation, and the awaited text absent from
957
+ // the page's own failure snapshot. A creation POST says nothing about what a collection render
958
+ // drops — m194's button failure was labelled missing while its title was on the page and the
959
+ // only carrier was \`POST /api/articles\` — and a snapshot that still shows the text says the
960
+ // opposite. Without that proof the note only says a response contained the text.
785
961
  const waitedText = waitedTextOf(failedError)
786
962
  let missingText = null
787
963
  if (waitedText !== null && !unattributed) {
964
+ let lastNavigation = null
965
+ for (const a of actions) {
966
+ if (!/\\.(?:goto|reload|goBack|goForward)$/.test(a.api)) continue
967
+ if (lastNavigation === null || a.start > lastNavigation) lastNavigation = a.start
968
+ }
788
969
  let best = null
970
+ let bestGet = null
789
971
  for (const r of requests) {
790
972
  if (r.type !== 'fetch' && r.type !== 'xhr') continue
791
973
  if (r.status < 200 || r.status >= 400) continue
792
974
  const got = gotText(buf, entries, r)
793
975
  if (!got || got.indexOf(waitedText) === -1) continue
794
976
  if (best === null || got.length < best.got.length) best = { request: r, got: got }
977
+ // The collection read the claim may rest on: a GET that started after the last navigation
978
+ // (a request that started before it belongs to the page load, not to the failure).
979
+ if (r.method !== 'GET') continue
980
+ if (lastNavigation === null || typeof r.startedAt !== 'number' || r.startedAt <= lastNavigation) continue
981
+ if (bestGet === null || got.length < bestGet.got.length) bestGet = { request: r, got: got }
982
+ }
983
+ if (best !== null) {
984
+ const named = bestGet === null ? best : bestGet
985
+ let dropped = false
986
+ if (bestGet !== null && frames.length > 0) {
987
+ const shown = snapshotShowed(frames[frames.length - 1])
988
+ dropped = shown !== null && shown.indexOf(waitedText) === -1
989
+ }
990
+ missingText = {
991
+ text: waitedText,
992
+ method: named.request.method,
993
+ path: named.request.path,
994
+ ...(dropped ? { dropped: true } : {}),
995
+ }
795
996
  }
796
- if (best !== null)
797
- missingText = { text: waitedText, method: best.request.method, path: best.request.path }
798
997
  }
998
+ // A visibility failure whose element RESOLVED but has an empty box: the snapshot's own DOM says
999
+ // what the element is made of, so the note names the cause instead of sending the run at the
1000
+ // selector (m197 — an <img> with no CSS size collapsed inside its link).
1001
+ const hiddenBox = hiddenBoxOf(failedError, frames)
799
1002
  function wire(r) {
800
1003
  return {
801
1004
  method: r.method,
@@ -1016,7 +1219,13 @@ for (const one of found.slice(0, MAX_TRACES)) {
1016
1219
  const specific = consoleErrors.filter(function (text) { return !/^Failed to load resource/.test(text) })
1017
1220
  const keptConsole = specific.concat(consoleSignals, consoleWarnings)
1018
1221
  if (specific.length === 0) keptConsole.push.apply(keptConsole, consoleErrors)
1019
- if (chosen.length === 0 && keptConsole.length === 0 && actions.length === 0) continue
1222
+ if (
1223
+ chosen.length === 0 &&
1224
+ keptConsole.length === 0 &&
1225
+ actions.length === 0 &&
1226
+ hiddenBox === null
1227
+ )
1228
+ continue
1020
1229
  process.stdout.write('TRACE ' + JSON.stringify({
1021
1230
  dir: path.relative(ROOT, path.dirname(one.file)),
1022
1231
  spec: spec,
@@ -1029,6 +1238,7 @@ for (const one of found.slice(0, MAX_TRACES)) {
1029
1238
  requests: chosen.slice(0, MAX_REQUESTS),
1030
1239
  ...(missingText === null ? {} : { missingText: missingText }),
1031
1240
  ...(readContradiction === null ? {} : { readContradiction: readContradiction }),
1241
+ ...(hiddenBox === null ? {} : { hiddenBox: hiddenBox }),
1032
1242
  console: keptConsole.slice(0, MAX_CONSOLE).map(function (text) { return text.slice(0, 300) }),
1033
1243
  actions: actions.slice(-MAX_ACTIONS),
1034
1244
  ...(unattributed ? { unattributed: true } : {}),
@@ -1081,7 +1291,12 @@ function parseMissingText(value) {
1081
1291
  return null;
1082
1292
  if (typeof raw.path !== 'string' || raw.path === '')
1083
1293
  return null;
1084
- return { text: raw.text, method: raw.method, path: raw.path };
1294
+ return {
1295
+ text: raw.text,
1296
+ method: raw.method,
1297
+ path: raw.path,
1298
+ ...(raw.dropped === true ? { dropped: true } : {}),
1299
+ };
1085
1300
  }
1086
1301
  /**
1087
1302
  * One `readContradiction` out of a `TRACE` line's json, or null when the value is not one.
@@ -1106,6 +1321,45 @@ function parseReadContradiction(value) {
1106
1321
  return null;
1107
1322
  return { field: raw.field, writeValue: raw.writeValue, readValue: raw.readValue, write, read };
1108
1323
  }
1324
+ /**
1325
+ * One `hiddenBox` out of a `TRACE` line's json, or null when the value is not one.
1326
+ *
1327
+ * The locator and tag are required; the image/empty/style detail is optional evidence, kept only
1328
+ * in the shape the script wrote it.
1329
+ *
1330
+ * @param value - The line's `hiddenBox`.
1331
+ * @returns The element the failed locator resolved to, or null.
1332
+ */
1333
+ function parseHiddenBox(value) {
1334
+ if (typeof value !== 'object' || value === null)
1335
+ return null;
1336
+ const raw = value;
1337
+ if (typeof raw.locator !== 'string' || raw.locator === '')
1338
+ return null;
1339
+ if (typeof raw.tag !== 'string' || raw.tag === '')
1340
+ return null;
1341
+ // An `image` that parses to no attributes is still the fact that the element wraps an <img>:
1342
+ // its presence is the evidence, so an empty object stays.
1343
+ const image = typeof raw.image === 'object' && raw.image !== null
1344
+ ? (() => {
1345
+ const at = raw.image;
1346
+ const out = {};
1347
+ for (const key of ['src', 'width', 'height', 'style', 'className', 'alt']) {
1348
+ const field = at[key];
1349
+ if (typeof field === 'string' && field !== '')
1350
+ out[key] = field;
1351
+ }
1352
+ return out;
1353
+ })()
1354
+ : null;
1355
+ return {
1356
+ locator: raw.locator,
1357
+ tag: raw.tag,
1358
+ ...(typeof raw.style === 'string' && raw.style ? { style: raw.style } : {}),
1359
+ ...(image === null ? {} : { image }),
1360
+ ...(raw.empty === true ? { empty: true } : {}),
1361
+ };
1362
+ }
1109
1363
  /**
1110
1364
  * One `lastAction` out of a `TRACE` line's json, or null when the value is not one.
1111
1365
  *
@@ -1153,6 +1407,7 @@ export function parseTraceArtifacts(stdout) {
1153
1407
  const lastAction = parseLastAction(raw.lastAction);
1154
1408
  const missingText = parseMissingText(raw.missingText);
1155
1409
  const readContradiction = parseReadContradiction(raw.readContradiction);
1410
+ const hiddenBox = parseHiddenBox(raw.hiddenBox);
1156
1411
  const otherContexts = Array.isArray(raw.otherContexts)
1157
1412
  ? raw.otherContexts
1158
1413
  .filter((c) => typeof c === 'object' && c !== null)
@@ -1190,6 +1445,7 @@ export function parseTraceArtifacts(stdout) {
1190
1445
  ...(raw.unattributed === true ? { unattributed: true } : {}),
1191
1446
  ...(missingText === null ? {} : { missingText }),
1192
1447
  ...(readContradiction === null ? {} : { readContradiction }),
1448
+ ...(hiddenBox === null ? {} : { hiddenBox }),
1193
1449
  });
1194
1450
  }
1195
1451
  catch (_error) {
@@ -2141,15 +2397,22 @@ function missingApiPrefixSentence(request) {
2141
2397
  /**
2142
2398
  * The sentence for a failing assertion whose awaited text a same-page API response carried.
2143
2399
  *
2144
- * It names the response and the fact the trace proves — the value reached the browser and the page
2145
- * did not render it — and stops there. Which cut drops it (a slice, a filter, a limit) is in the
2146
- * component that maps the response, and the note cannot see that component; naming the mapping
2147
- * step is the whole instruction (xr-molecule-flash-m169).
2400
+ * With proof ({@link TraceMissingText.dropped}) it names the response and the fact the trace
2401
+ * proves — the value reached the browser and the page did not render it — and stops there. Which
2402
+ * cut drops it (a slice, a filter, a limit) is in the component that maps the response, and the
2403
+ * note cannot see that component; naming the mapping step is the whole instruction
2404
+ * (xr-molecule-flash-m169).
2405
+ *
2406
+ * Without that proof the sentence says only what the trace shows — a response contained the text —
2407
+ * and makes no rendering claim: m194's awaited title was on the page, in a disabled button, while
2408
+ * the only response carrying it was the creation POST (a button failure is not a missing article).
2148
2409
  *
2149
2410
  * @param missing - The awaited text and the response that carried it.
2150
2411
  * @returns The sentence the note leads with.
2151
2412
  */
2152
2413
  function missingTextSentence(missing) {
2414
+ if (missing.dropped !== true)
2415
+ return `The response ${missing.method} ${missing.path} contained "${missing.text}".`;
2153
2416
  return (`The API returned "${missing.text}" (in ${missing.method} ${missing.path}), but the page did ` +
2154
2417
  `not render it — the page drops it before rendering (a slice/filter/limit in the component ` +
2155
2418
  `that renders that list); find where that response is mapped.`);
@@ -2344,6 +2607,71 @@ function consoleLine(text) {
2344
2607
  return (`${url} answered the app's index.html, not a font — the file is missing from ` +
2345
2608
  `\`app/public/\` (or the asset import); copy/import it`);
2346
2609
  }
2610
+ /** An `<img>` as the failure snapshot recorded it, for the note's sentence. */
2611
+ function imageMarkup(image) {
2612
+ const attrs = [
2613
+ image.src === undefined ? '' : `src="${image.src}"`,
2614
+ image.alt === undefined ? '' : `alt="${image.alt}"`,
2615
+ image.width === undefined ? '' : `width="${image.width}"`,
2616
+ image.height === undefined ? '' : `height="${image.height}"`,
2617
+ image.className === undefined ? '' : `class="${image.className}"`,
2618
+ image.style === undefined ? '' : `style="${image.style}"`,
2619
+ ].filter(Boolean);
2620
+ return `<img${attrs.length > 0 ? ` ${attrs.join(' ')}` : ''}>`;
2621
+ }
2622
+ /** Whether an image's CSS (its class or inline style) declares a width or height. */
2623
+ const IMAGE_SIZE_RULE = /(?:^|;)\s*(?:width|height|max-width|max-height)\s*:/;
2624
+ const HIDING_RULE = /(?:^|;)\s*(?:display\s*:\s*none|visibility\s*:\s*hidden)/;
2625
+ /**
2626
+ * The sentence a "hidden" visibility failure earns when the trace proved WHY the box is empty.
2627
+ *
2628
+ * Playwright calls an element with a zero-size box hidden even when it is painted, so the answer
2629
+ * is in the element's children. `a.navbar-brand` on xr-molecule-flash-m197 was 0×4 px because its
2630
+ * only child was `<img src="/conduit-logo.svg" height="26">`: an SVG with a `viewBox` and no
2631
+ * width/height has no intrinsic size, and the `height` attribute loses to Tailwind's preflight
2632
+ * (`img { max-width: 100%; height: auto }`), which the scaffold's own `index.css` imports. The
2633
+ * note said only "the cause is in the page", and the run spent over four minutes rasterising an
2634
+ * SVG to PNG. Only what the snapshot shows is claimed ({@link TraceHiddenBox}).
2635
+ *
2636
+ * @param items - The traces {@link parseTraceArtifacts} read.
2637
+ * @returns The sentence, or null when no failure carries a hidden-box reading.
2638
+ */
2639
+ export function hiddenBoxNote(items) {
2640
+ const box = items.find((item) => item.hiddenBox !== undefined)?.hiddenBox;
2641
+ if (box === undefined)
2642
+ return null;
2643
+ const element = `\`${box.locator}\``;
2644
+ if (box.image !== undefined) {
2645
+ const image = box.image;
2646
+ const shown = imageMarkup(image);
2647
+ const sized = image.className !== undefined ||
2648
+ (image.style !== undefined && IMAGE_SIZE_RULE.test(image.style));
2649
+ if (sized) {
2650
+ const rule = image.className === undefined ? 'its inline size' : `\`${image.className}\``;
2651
+ return (`${element} resolved but Playwright calls it hidden: its box has no size, and its only ` +
2652
+ `content is ${shown}. That image declares a size (${rule}), so the rule is not applying ` +
2653
+ `on this page — check that the stylesheet defining it is actually loaded (a supplied ` +
2654
+ `theme has to be linked in index.html, not only imported) and that the class name is ` +
2655
+ `spelled the way the theme defines it.`);
2656
+ }
2657
+ return (`${element} resolved but Playwright calls it hidden: the element's box is empty, and its ` +
2658
+ `only content is ${shown} — an image with no CSS size. An <img> takes its size from CSS or ` +
2659
+ `from the file; an SVG that declares only a viewBox has no intrinsic size, and a ` +
2660
+ `width/height ATTRIBUTE loses to any stylesheet rule (Tailwind's preflight, which the ` +
2661
+ `scaffold's index.css imports, sets img { max-width: 100%; height: auto }). Give the image a ` +
2662
+ `CSS width/height — a class the supplied theme already defines for it, or ` +
2663
+ `style={{ height: … }} — rather than changing the SVG or rasterising it to PNG.`);
2664
+ }
2665
+ if (box.style !== undefined && HIDING_RULE.test(box.style)) {
2666
+ return (`${element} resolved but Playwright calls it hidden: its own inline style sets ` +
2667
+ `\`${box.style}\`. Remove that declaration (or the rule that sets it) so the element has a box.`);
2668
+ }
2669
+ if (box.empty === true) {
2670
+ return (`${element} resolved but Playwright calls it hidden: the element has no text and no ` +
2671
+ `children, so its box has zero size. Give it content or an explicit width/height.`);
2672
+ }
2673
+ return null;
2674
+ }
2347
2675
  /**
2348
2676
  * The note a failed suite run's result carries: what each failing test's page actually sent, and
2349
2677
  * what came back.
@@ -2482,6 +2810,10 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
2482
2810
  const wrapped = traced.some((item) => item.requests.some((request) => request.sent !== undefined && wrappedInData(request.sent)));
2483
2811
  // The "one of these requests failing" answer is only honest when a listed request really failed.
2484
2812
  const anyFailed = traced.some((item) => item.requests.some(failedRequest));
2813
+ // A visibility failure the trace's own snapshot explains: said instead of the generic
2814
+ // "the cause is in the page" sentence, which m197 read as "go look at the page" and then spent
2815
+ // four minutes rasterising an SVG ({@link hiddenBoxNote}).
2816
+ const hiddenBox = hiddenBoxNote(traced);
2485
2817
  // m145's shape: the app's HTTP client lost its base URL, so every API call reached the page
2486
2818
  // origin without the prefix the app mounts its routes under. The prefix is READ — the caller's
2487
2819
  // client config plus what the traces show — never assumed to be `/api`.
@@ -2746,14 +3078,19 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
2746
3078
  // first, before any row, is that the value arrived and the page dropped it (m169's missing
2747
3079
  // "trending" came back from GET /api/tags while the sidebar rendered a cut list). Each distinct
2748
3080
  // response is named once; the note makes no claim about which cut did it.
2749
- const missingTexts = [
2750
- ...new Map(traced
2751
- .filter((item) => item.missingText !== undefined)
2752
- .map((item) => {
2753
- const missing = item.missingText;
2754
- return [`${missing.method} ${missing.path}\u0000${missing.text}`, missing];
2755
- })).values(),
2756
- ];
3081
+ // One sentence per response; a trace that PROVES the drop upgrades one that only saw the same
3082
+ // response carry the text.
3083
+ const missingByResponse = new Map();
3084
+ for (const item of traced) {
3085
+ if (item.missingText === undefined)
3086
+ continue;
3087
+ const missing = item.missingText;
3088
+ const key = `${missing.method} ${missing.path}\u0000${missing.text}`;
3089
+ const seen = missingByResponse.get(key);
3090
+ if (seen === undefined || (seen.dropped !== true && missing.dropped === true))
3091
+ missingByResponse.set(key, missing);
3092
+ }
3093
+ const missingTexts = [...missingByResponse.values()];
2757
3094
  const missingTextLead = missingTexts.length === 0
2758
3095
  ? ''
2759
3096
  : `${missingTexts.slice(0, 2).map(missingTextSentence).join(' ')}\n\n`;
@@ -2841,13 +3178,17 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
2841
3178
  'action sent no request at all, so there is no response to read. The page changed ' +
2842
3179
  'route and fetched nothing, which is why it still shows the previous route’s data: ' +
2843
3180
  'fix how the page reads the route, not the selector.'
2844
- : 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
2845
- "page, not the API. Start with what the test's last action sent (the first line of each " +
2846
- 'row) and what the page did next: a request that answered (2xx included) while the page ' +
2847
- 'still did not show the state the assertion waited on is the fact to read — its status ' +
2848
- 'and response body, not the selector. If the request is not the one the test meant — an ' +
2849
- 'unfollow sent where the test clicked follow — the state the action read was stale ' +
2850
- 'before it.') +
3181
+ : hiddenBox !== null
3182
+ ? // The trace's own snapshot explains why the element Playwright resolved has no box:
3183
+ // a fact about the element, so it replaces the "look at the page" advice (m197).
3184
+ hiddenBox
3185
+ : 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
3186
+ "page, not the API. Start with what the test's last action sent (the first line of each " +
3187
+ 'row) and what the page did next: a request that answered (2xx included) while the page ' +
3188
+ 'still did not show the state the assertion waited on is the fact to read — its status ' +
3189
+ 'and response body, not the selector. If the request is not the one the test meant — an ' +
3190
+ 'unfollow sent where the test clicked follow — the state the action read was stale ' +
3191
+ 'before it.') +
2851
3192
  prefixAdvice +
2852
3193
  (wrapped
2853
3194
  ? ' The API received a body wrapped in `data`: app-http’s `post(url, data, config)` / ' +
package/dist/types.d.ts CHANGED
@@ -63,14 +63,34 @@ export interface SuiteBudgetPin {
63
63
  /**
64
64
  * The Playwright flags that carry the suite's own settings onto the command
65
65
  * (`--timeout=15000`, `--workers=1`), as the consumer's own scan reads them. Empty when the
66
- * difference is in settings no flag carries — the pin is then the sentence alone.
66
+ * difference is in settings no flag carries.
67
67
  */
68
68
  flags: string[];
69
+ /** One sentence for the run's result: which of the suite's own settings this run uses. */
70
+ note: string;
69
71
  /**
70
- * One sentence for the run's result: this run uses the suite's own budget, and which settings
71
- * a config keeps because Playwright has no flag for them.
72
+ * A config file the consumer wrote OUTSIDE the supplied suite, which re-imports the config the
73
+ * run selected and pins the settings no Playwright flag can carry (`expect.timeout`,
74
+ * `use.actionTimeout`, `use.navigationTimeout`). When present, the run is launched with
75
+ * `--config <this path>` in place of the config it named, so those settings are the suite's own
76
+ * rather than the executor's. Absent when the consumer could not write one.
72
77
  */
73
- note: string;
78
+ config?: string;
79
+ /**
80
+ * Every setting the derived config ({@link config}) pins at the suite's own value, by the scan's
81
+ * key (`timeout`, `expect.timeout`, `actionTimeout`, `navigationTimeout`), in milliseconds as
82
+ * digits. Empty when no derived config was placed.
83
+ */
84
+ values?: Record<string, string>;
85
+ /**
86
+ * Every setting the run's own config makes different from the suite's, as the consumer's scan
87
+ * read them: the value this run's config writes and the suite's own. The tool reports the ones
88
+ * its placement did NOT put back, so a result never claims a drift it carried.
89
+ */
90
+ drift?: Record<string, {
91
+ used: string;
92
+ suite: string;
93
+ }>;
74
94
  }
75
95
  /**
76
96
  * Configuration for building the tool set.
@@ -188,7 +208,8 @@ export interface ToolBuildConfig {
188
208
  * different settings, asked BEFORE the run executes. The consumer knows where the suite and its
189
209
  * base config live; this shared package does not, so the reading is the consumer's and the
190
210
  * mechanics — putting the flags on the runner's own arguments, before the pipeline and outside
191
- * any redirect or group closer — are this tool's.
211
+ * any redirect or group closer, and pointing `--config` at the derived config that carries the
212
+ * settings no flag can — are this tool's.
192
213
  *
193
214
  * Called for a Playwright suite command only, and only the LAST suite step is touched. A run
194
215
  * that already keeps the suite's settings, or a sandbox with no supplied suite, answers `null`.
@@ -196,7 +217,7 @@ export interface ToolBuildConfig {
196
217
  *
197
218
  * @param command - The shell command, as the model sent it.
198
219
  * @param cwd - The resolved working directory it will run in.
199
- * @returns The flags and the sentence, or null when nothing is put back.
220
+ * @returns The flags, the derived config and the sentence, or null when nothing is put back.
200
221
  */
201
222
  suiteBudget?: (command: string, cwd: string) => Promise<SuiteBudgetPin | null | undefined>;
202
223
  /** Post-write hook (e.g. auto-format via Prettier/ESLint). Called after every write_file/edit_file. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}