Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,14 +234,17 @@

- **autoStop** (boolean) **(required)**: Determines if the trace recording should be automatically stopped.
- **reload** (boolean) **(required)**: Determines if, once tracing has started, the page should be automatically reloaded.
- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the raw trace data.

---

### `performance_stop_trace`

**Description:** Stops the active performance trace recording on the selected page.

**Parameters:** None
**Parameters:**

- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the raw trace data.

---

Expand Down
42 changes: 36 additions & 6 deletions src/tools/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const startTrace = defineTool({
'Starts a performance trace recording on the selected page. This can be used to look for performance problems and insights to improve the performance of the page. It will also report Core Web Vital (CWV) scores for the page.',
annotations: {
category: ToolCategory.PERFORMANCE,
readOnlyHint: true,
readOnlyHint: false,
},
schema: {
reload: zod
Expand All @@ -38,6 +38,12 @@ export const startTrace = defineTool({
.describe(
'Determines if the trace recording should be automatically stopped.',
),
filePath: zod
.string()
.optional()
.describe(
'The absolute path, or a path relative to the current working directory, to save the raw trace data.',
),
},
handler: async (request, response, context) => {
if (context.isRunningPerformanceTrace()) {
Expand Down Expand Up @@ -91,7 +97,12 @@ export const startTrace = defineTool({

if (request.params.autoStop) {
await new Promise(resolve => setTimeout(resolve, 5_000));
await stopTracingAndAppendOutput(page, response, context);
await stopTracingAndAppendOutput(
page,
response,
context,
request.params.filePath,
);
} else {
response.appendResponseLine(
`The performance trace is being recorded. Use performance_stop_trace to stop it.`,
Expand All @@ -106,15 +117,27 @@ export const stopTrace = defineTool({
'Stops the active performance trace recording on the selected page.',
annotations: {
category: ToolCategory.PERFORMANCE,
readOnlyHint: true,
readOnlyHint: false,
},
schema: {
filePath: zod
.string()
.optional()
.describe(
'The absolute path, or a path relative to the current working directory, to save the raw trace data.',
),
},
schema: {},
handler: async (_request, response, context) => {
handler: async (request, response, context) => {
if (!context.isRunningPerformanceTrace()) {
return;
}
const page = context.getSelectedPage();
await stopTracingAndAppendOutput(page, response, context);
await stopTracingAndAppendOutput(
page,
response,
context,
request.params.filePath,
);
},
});

Expand Down Expand Up @@ -165,9 +188,16 @@ async function stopTracingAndAppendOutput(
page: Page,
response: Response,
context: Context,
filePath?: string,
): Promise<void> {
try {
const traceEventsBuffer = await page.tracing.stop();
if (filePath && traceEventsBuffer) {
const file = await context.saveFile(traceEventsBuffer, filePath);
response.appendResponseLine(
`The raw trace data was saved to ${file.filename}.`,
);
}
const result = await parseRawTraceBuffer(traceEventsBuffer);
response.appendResponseLine('The performance trace has been stopped.');
if (traceResultIsSuccess(result)) {
Expand Down
62 changes: 62 additions & 0 deletions tests/tools/performance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,42 @@ describe('performance', () => {
);
});
});

it('supports filePath', async () => {
const rawData = loadTraceAsBuffer('basic-trace.json.gz');
await withMcpContext(async (response, context) => {
const filePath = 'test-trace.json';
const selectedPage = context.getSelectedPage();
sinon.stub(selectedPage, 'url').callsFake(() => 'https://www.test.com');
sinon.stub(selectedPage, 'goto').callsFake(() => Promise.resolve(null));
sinon.stub(selectedPage.tracing, 'start');
sinon.stub(selectedPage.tracing, 'stop').resolves(rawData);
const saveFileStub = sinon
.stub(context, 'saveFile')
.resolves({filename: filePath});

const clock = sinon.useFakeTimers({shouldClearNativeTimers: true});
try {
const handlerPromise = startTrace.handler(
{params: {reload: true, autoStop: true, filePath}},
response,
context,
);
await clock.tickAsync(6_000);
await handlerPromise;

assert.ok(
response.responseLines.includes(
`The raw trace data was saved to ${filePath}.`,
),
);
sinon.assert.calledOnce(saveFileStub);
sinon.assert.calledWith(saveFileStub, rawData, filePath);
} finally {
clock.restore();
}
});
});
});

describe('performance_analyze_insight', () => {
Expand Down Expand Up @@ -275,5 +311,31 @@ describe('performance', () => {
t.assert.snapshot?.(response.responseLines.join('\n'));
});
});

it('supports filePath', async () => {
const rawData = loadTraceAsBuffer('basic-trace.json.gz');
await withMcpContext(async (response, context) => {
const filePath = 'test-trace.json';
context.setIsRunningPerformanceTrace(true);
const selectedPage = context.getSelectedPage();
const stopTracingStub = sinon
.stub(selectedPage.tracing, 'stop')
.resolves(rawData);
const saveFileStub = sinon
.stub(context, 'saveFile')
.resolves({filename: filePath});

await stopTrace.handler({params: {filePath}}, response, context);

sinon.assert.calledOnce(stopTracingStub);
sinon.assert.calledOnce(saveFileStub);
sinon.assert.calledWith(saveFileStub, rawData, filePath);
assert.ok(
response.responseLines.includes(
`The raw trace data was saved to ${filePath}.`,
),
);
});
});
});
});
Loading