Skip to content

Commit 1aaea9b

Browse files
DavertMikclaude
andcommitted
feat(mcp): surface aiTrace dir in run_test / pause payloads
run_test, run_step_by_step, and pausedPayload now include aiTraceDir (the per-test output/trace_<title>_<hash>/ folder) so agents can point codeceptq directly at the saved *_page.html snapshots without globbing or recomputing the hash. Per-test entries in reporterJson.tests[] also carry the dir. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 557941d commit 1aaea9b

1 file changed

Lines changed: 23 additions & 0 deletions

File tree

bin/mcp-server.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ function outputBaseDir() {
237237
return global.output_dir || resolvePath(process.cwd(), 'output')
238238
}
239239

240+
function computeTraceDir(test) {
241+
const title = test.fullTitle ? test.fullTitle() : test.title
242+
return traceDirFor(test.file, title, outputBaseDir())
243+
}
244+
240245
// In-process pause coordination. When a test running through run_test calls
241246
// pause(), the handler registered via setPauseHandler resolves a "paused"
242247
// promise that run_test is racing against test completion. The "pause" tool
@@ -247,6 +252,7 @@ let pendingRunResults = null // results array being collected while paused
247252
let pendingRunCleanup = null // cleanup callback to detach test.after / step.after listeners
248253
let pendingTestFile = null // file path of the test currently running
249254
let pendingStepInfo = null // { index, name, status } of the last step that fired step.after
255+
let pendingTraceDir = null // aiTrace per-test dir for the running test
250256
const pauseEvents = new EventEmitter()
251257

252258
setPauseHandler(({ registeredVariables }) => {
@@ -293,13 +299,16 @@ function collectRunCompletion(errorMessage) {
293299
passes: results.filter(r => r.status === 'passed').length,
294300
failures: results.filter(r => r.status === 'failed').length,
295301
}
302+
const aiTraceDir = pendingTraceDir
296303
if (typeof pendingRunCleanup === 'function') pendingRunCleanup()
297304
pendingRunPromise = null
298305
pendingRunResults = null
299306
pendingTestFile = null
300307
pendingStepInfo = null
308+
pendingTraceDir = null
301309
return {
302310
status: 'completed',
311+
aiTraceDir,
303312
reporterJson: { stats, tests: results },
304313
error: errorMessage,
305314
}
@@ -309,11 +318,13 @@ function pausedPayload() {
309318
return {
310319
status: 'paused',
311320
file: pendingTestFile,
321+
aiTraceDir: pendingTraceDir,
312322
pausedAfter: pendingStepInfo,
313323
suggestions: [
314324
'Call snapshot to capture URL/HTML/ARIA/screenshot/console/storage at this point',
315325
'Call run_code to inspect or manipulate state (e.g. return await I.grabText("h1"))',
316326
'Call continue to release the pause and let the test run the next step (or finish)',
327+
'Query a saved step snapshot offline: codeceptq <locator> --file <aiTraceDir>/<NNNN>_<step>_page.html',
317328
],
318329
}
319330
}
@@ -709,13 +720,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
709720
pendingStepInfo = null
710721
let stepIndex = 0
711722

723+
const onBefore = t => {
724+
pendingTraceDir = computeTraceDir(t)
725+
}
712726
const onAfter = t => {
713727
pendingRunResults.push({
714728
title: t.title,
715729
file: t.file,
716730
status: t.err ? 'failed' : 'passed',
717731
error: t.err?.message,
718732
duration: t.duration,
733+
aiTraceDir: pendingTraceDir,
719734
})
720735
}
721736
const onStepAfter = step => {
@@ -729,9 +744,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
729744
pauseNow()
730745
}
731746
}
747+
event.dispatcher.on(event.test.before, onBefore)
732748
event.dispatcher.on(event.test.after, onAfter)
733749
event.dispatcher.on(event.step.after, onStepAfter)
734750
pendingRunCleanup = () => {
751+
try { event.dispatcher.removeListener(event.test.before, onBefore) } catch {}
735752
try { event.dispatcher.removeListener(event.test.after, onAfter) } catch {}
736753
try { event.dispatcher.removeListener(event.step.after, onStepAfter) } catch {}
737754
pendingRunCleanup = null
@@ -802,13 +819,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
802819
pendingStepInfo = null
803820
let stepIndex = 0
804821

822+
const onBefore = t => {
823+
pendingTraceDir = computeTraceDir(t)
824+
}
805825
const onAfter = t => {
806826
pendingRunResults.push({
807827
title: t.title,
808828
file: t.file,
809829
status: t.err ? 'failed' : 'passed',
810830
error: t.err?.message,
811831
duration: t.duration,
832+
aiTraceDir: pendingTraceDir,
812833
})
813834
}
814835
const onStepAfter = step => {
@@ -821,9 +842,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
821842
// Pause after every step — agent calls continue to advance.
822843
pauseNow()
823844
}
845+
event.dispatcher.on(event.test.before, onBefore)
824846
event.dispatcher.on(event.test.after, onAfter)
825847
event.dispatcher.on(event.step.after, onStepAfter)
826848
pendingRunCleanup = () => {
849+
try { event.dispatcher.removeListener(event.test.before, onBefore) } catch {}
827850
try { event.dispatcher.removeListener(event.test.after, onAfter) } catch {}
828851
try { event.dispatcher.removeListener(event.step.after, onStepAfter) } catch {}
829852
pendingRunCleanup = null

0 commit comments

Comments
 (0)