chrome-devtools-frontend
Version:
Chrome DevTools UI
98 lines (82 loc) • 4.16 kB
text/typescript
// Copyright 2025 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {Page} from 'puppeteer-core'; // ElementHandle removed as it's not directly used by this class anymore
import type {IndividualPromptRequestResponse, TestTarget} from '../../types.d.ts';
import {
executePromptCycle,
extractCommentMetadata,
loadPerformanceTrace,
stripCommentsFromPage
} from '../shared/puppeteer-helpers.ts';
import type {TraceDownloader} from '../trace-downloader.ts';
import type {TargetExecutor, TargetPreparationResult} from './interface.ts';
export class PerformanceInsightsExecutor implements TargetExecutor {
readonly #traceDownloader: TraceDownloader;
constructor(traceDownloader: TraceDownloader) {
this.#traceDownloader = traceDownloader;
}
async prepare(exampleUrl: string, page: Page, devtoolsPage: Page, commonLog: (text: string) => void, userArgs: {
includeFollowUp: boolean,
testTarget: TestTarget,
}): Promise<TargetPreparationResult> {
commonLog(`[PerfInsightsExecutor] Preparing example: ${exampleUrl} for target: ${userArgs.testTarget}`);
await loadPerformanceTrace(devtoolsPage, this.#traceDownloader, exampleUrl, page, commonLog);
const metadata = await extractCommentMetadata(page, userArgs.includeFollowUp, commonLog);
if (!metadata.rawComment.insight) {
throw new Error(
'[PerfInsightsExecutor] Cannot execute performance-insights example without "Insight:" in example comment metadata.');
}
const insightTitle = metadata.rawComment.insight;
// Select Insight in Performance Panel
commonLog('[PerfInsightsExecutor] Selecting insight in the performance panel...');
await devtoolsPage.locator(':scope >>> #tab-timeline').setTimeout(5000).click();
const sidebarButton = await devtoolsPage.$('aria/Show sidebar');
if (sidebarButton) {
await sidebarButton.click();
commonLog('[PerfInsightsExecutor] Opened Performance panel sidebar.');
} else {
commonLog('[PerfInsightsExecutor] Performance panel sidebar already open or not found.');
}
commonLog(`[PerfInsightsExecutor] Expanding Insight: ${insightTitle}`);
// Now find the header for the right insight, and click to expand it.
// Note that we can't use aria here because the aria-label for insights
// can be extended to include estimated savings. So we use the data
// attribute instead. The title is JSON so it is already wrapped with double quotes.
await devtoolsPage.locator(`:scope >>> [data-insight-header-title=${insightTitle}]`).setTimeout(10_000).click();
commonLog('[PerfInsightsExecutor] Clicked on insight header.');
// Open AI Assistance Panel via "Ask AI" button
commonLog('[PerfInsightsExecutor] Clicking "Ask AI" button for the insight...');
await devtoolsPage.locator(':scope >>> devtools-button[data-insights-ask-ai]').setTimeout(5000).click();
commonLog('[PerfInsightsExecutor] AI Assistance panel opened via insights button.');
await stripCommentsFromPage(page, commonLog);
return {
queries: metadata.queries,
explanation: metadata.explanation,
rawComment: metadata.rawComment,
};
}
async execute(
devtoolsPage: Page,
preparationResult: TargetPreparationResult,
exampleId: string,
commonLog: (text: string) => void,
): Promise<IndividualPromptRequestResponse[]> {
const allResults: IndividualPromptRequestResponse[] = [];
const inputSelector = 'aria/Ask a question about the selected performance insight';
for (const query of preparationResult.queries) {
commonLog(`[PerfInsightsExecutor] Executing query: "${query}" for example: ${exampleId}`);
const results = await executePromptCycle(
devtoolsPage,
query,
inputSelector,
exampleId,
/* isMultimodal */ false,
commonLog,
);
allResults.push(...results);
}
commonLog(`[PerfInsightsExecutor] Finished executing all queries for example: ${exampleId}`);
return allResults;
}
}