Merge pull request #1451 from pikasTech/fix/2351-webprobe-performance-evidence
fix: surface WebProbe performance evidence mode
This commit is contained in:
@@ -46,6 +46,7 @@ function buildFrontendPerformanceReport(rows, artifacts) {
|
||||
.map(finalizeProfileStackHotspot)
|
||||
.sort((left, right) => Number(right.selfTimeMs ?? 0) - Number(left.selfTimeMs ?? 0))
|
||||
.slice(0, 30);
|
||||
const attribution = frontendPerformanceAttributionStatus({ captures, profileHotspots, profileStacks, scriptHotspots, longTasks, loafs, gaps });
|
||||
return {
|
||||
summary: {
|
||||
rowCount: sourceRows.length,
|
||||
@@ -63,6 +64,12 @@ function buildFrontendPerformanceReport(rows, artifacts) {
|
||||
maxLongTaskMs: frontendPerformanceMaxNumber(longTasks, (item) => item.durationMs),
|
||||
maxLongAnimationFrameMs: frontendPerformanceMaxNumber(loafs, (item) => item.durationMs),
|
||||
maxEventLoopGapMs: frontendPerformanceMaxNumber(gaps, (item) => item.durationMs),
|
||||
cpuProfileStatus: attribution.cpuProfileStatus,
|
||||
attributionMode: attribution.attributionMode,
|
||||
noCpuProfile: attribution.noCpuProfile,
|
||||
loafOnly: attribution.loafOnly,
|
||||
cpuProfileHotspotEvidence: attribution.cpuProfileHotspotEvidence,
|
||||
evidenceNote: attribution.evidenceNote,
|
||||
captureArtifacts: performanceCaptureArtifacts(artifacts),
|
||||
valuesRedacted: true,
|
||||
},
|
||||
@@ -127,6 +134,21 @@ function buildFrontendPerformanceFindings(report) {
|
||||
topStacks: (report?.profileStacks || []).slice(0, 8),
|
||||
valuesRedacted: true,
|
||||
});
|
||||
if (summary.noCpuProfile === true && (severeLongTasks.length > 0 || severeLoafs.length > 0 || severeGaps.length > 0)) findings.push({
|
||||
id: "frontend-performance-loaf-only-no-cpu-profile",
|
||||
severity: "amber",
|
||||
summary: "frontend performance evidence is LoAF/LongTask/event-loop only because no completed CPU profile capture is present; do not cite CPU profile hotspots for this run",
|
||||
count: 1,
|
||||
attributionMode: summary.attributionMode || "loaf-only-no-cpu-profile",
|
||||
cpuProfileStatus: summary.cpuProfileStatus || "missing",
|
||||
captureCount: summary.captureCount ?? 0,
|
||||
longTaskCount: summary.longTaskCount ?? 0,
|
||||
longAnimationFrameCount: summary.longAnimationFrameCount ?? 0,
|
||||
eventLoopGapCount: summary.eventLoopGapCount ?? 0,
|
||||
topScripts: (report?.scriptHotspots || []).slice(0, 12),
|
||||
nextAction: "Run an explicit performanceCapture command and re-run observe analyze before making CPU-profile hotspot claims; existing LoAF scripts remain valid browser-side attribution.",
|
||||
valuesRedacted: true,
|
||||
});
|
||||
if ((report?.drainErrors || []).length > 0) findings.push({
|
||||
id: "frontend-performance-probe-drain-errors",
|
||||
severity: "amber",
|
||||
@@ -138,6 +160,43 @@ function buildFrontendPerformanceFindings(report) {
|
||||
return findings;
|
||||
}
|
||||
|
||||
function frontendPerformanceAttributionStatus(input) {
|
||||
const captureCount = Array.isArray(input?.captures) ? input.captures.length : 0;
|
||||
const profileHotspotCount = Array.isArray(input?.profileHotspots) ? input.profileHotspots.length : 0;
|
||||
const profileStackCount = Array.isArray(input?.profileStacks) ? input.profileStacks.length : 0;
|
||||
const scriptHotspotCount = Array.isArray(input?.scriptHotspots) ? input.scriptHotspots.length : 0;
|
||||
const eventCount =
|
||||
(Array.isArray(input?.longTasks) ? input.longTasks.length : 0) +
|
||||
(Array.isArray(input?.loafs) ? input.loafs.length : 0) +
|
||||
(Array.isArray(input?.gaps) ? input.gaps.length : 0);
|
||||
const hasCpuProfile = captureCount > 0;
|
||||
const hasCpuProfileHotspots = profileHotspotCount > 0 || profileStackCount > 0;
|
||||
if (hasCpuProfile) {
|
||||
return {
|
||||
cpuProfileStatus: hasCpuProfileHotspots ? "captured-with-hotspots" : "captured-no-hotspots",
|
||||
attributionMode: "cpu-profile-and-performance-observer",
|
||||
noCpuProfile: false,
|
||||
loafOnly: false,
|
||||
cpuProfileHotspotEvidence: hasCpuProfileHotspots,
|
||||
evidenceNote: hasCpuProfileHotspots
|
||||
? "completed performanceCapture artifacts produced CPU profile hotspot evidence"
|
||||
: "completed performanceCapture artifacts exist, but no CPU profile hotspots were extracted",
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
cpuProfileStatus: "missing",
|
||||
attributionMode: eventCount > 0 || scriptHotspotCount > 0 ? "loaf-only-no-cpu-profile" : "no-frontend-performance-evidence",
|
||||
noCpuProfile: true,
|
||||
loafOnly: eventCount > 0 || scriptHotspotCount > 0,
|
||||
cpuProfileHotspotEvidence: false,
|
||||
evidenceNote: eventCount > 0 || scriptHotspotCount > 0
|
||||
? "LongTask/LoAF/event-loop evidence is present, but no completed performanceCapture CPU profile exists"
|
||||
: "no frontend performance events or completed performanceCapture CPU profile exist",
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function compactPerformanceEventRow(row, perf) {
|
||||
return {
|
||||
ts: row.ts ?? null,
|
||||
|
||||
@@ -46,6 +46,7 @@ const network=readJsonl('network.jsonl');
|
||||
const browserProcess=readJsonl('browser-process.jsonl');
|
||||
const performanceRows=readJsonl('performance-events.jsonl');
|
||||
const manifest=readJson('manifest.json')||{};
|
||||
const heartbeat=readJson('heartbeat.json')||{};
|
||||
const report=readJson('analysis/report.json')||{};
|
||||
function unique(values){return Array.from(new Set(values.filter(Boolean)));}
|
||||
function numOrNull(value){const n=Number(value); return Number.isFinite(n)?n:null}
|
||||
@@ -746,13 +747,68 @@ function compactPerfCapture(item){
|
||||
function compactPerfFinding(item){
|
||||
return {severity:item?.severity??item?.level??null,id:short(item?.id||item?.kind||item?.code||'',56),count:item?.count??item?.sampleCount??null,rootCause:item?.rootCause??item?.rootCauseStatus??null,summary:short(item?.summary||item?.message||'',110),valuesRedacted:true};
|
||||
}
|
||||
function compactPerfCommand(row){
|
||||
return {bucket:row?.bucket||null,id:short(commandFileId(row)||'',40),type:short(commandFileType(row)||'',32),ts:short(commandFileTs(row)||'',32),ageSeconds:row?.data?.ageSeconds??null,relative:short(row?.relative||'',72),valuesRedacted:true};
|
||||
}
|
||||
function runnerStatusForPerformance(){
|
||||
const status=String(report?.heartbeat?.status||heartbeat?.status||report?.manifest?.status||manifest?.status||'').trim();
|
||||
const terminal=/^(completed|failed|force-stopped|stopped|abandoned)$/u.test(status);
|
||||
const failed=/^(failed|force-stopped|abandoned|not-running)$/u.test(status);
|
||||
return {status:status||null,terminal,notRunningOrFailed:failed,updatedAt:report?.heartbeat?.updatedAt||heartbeat?.updatedAt||heartbeat?.lastSampleAt||null,valuesRedacted:true};
|
||||
}
|
||||
function performanceCaptureCommandStatus(commandFiles){
|
||||
const rows=commandFiles.filter((row)=>commandFileType(row)==='performanceCapture');
|
||||
const byBucket=(bucket)=>rows.filter((row)=>row.bucket===bucket);
|
||||
return {
|
||||
total:rows.length,
|
||||
pendingCount:byBucket('pending').length,
|
||||
processingCount:byBucket('processing').length,
|
||||
doneCount:byBucket('done').length,
|
||||
failedCount:byBucket('failed').length,
|
||||
abandonedCount:byBucket('abandoned').length,
|
||||
pending:byBucket('pending').slice(0,6).map(compactPerfCommand),
|
||||
processing:byBucket('processing').slice(0,6).map(compactPerfCommand),
|
||||
failed:byBucket('failed').slice(-4).map(compactPerfCommand),
|
||||
done:byBucket('done').slice(-4).map(compactPerfCommand),
|
||||
valuesRedacted:true
|
||||
};
|
||||
}
|
||||
function performanceToolFindings(){
|
||||
const ids=new Set(['tool-pending-commands-unconsumed','tool-runner-heartbeat-stale','tool-target-page-not-ready','tool-runner-force-stopped','frontend-browser-freeze-runner-blocker','frontend-playwright-responsiveness-red','frontend-cdp-metrics-timeout-red','frontend-performance-probe-drain-errors','frontend-performance-loaf-only-no-cpu-profile']);
|
||||
return (Array.isArray(report.findings)?report.findings:[]).filter((item)=>ids.has(String(item?.id||item?.kind||item?.code||''))).slice(0,10).map(compactPerfFinding);
|
||||
}
|
||||
function performanceEvidenceMode(perf, commandFiles){
|
||||
const s=perf.summary||{};
|
||||
const captureCount=Number(s.captureCount??0);
|
||||
const hasCpuProfile=captureCount>0;
|
||||
const hasPerformanceObserverEvidence=
|
||||
Number(s.longTaskCount??0)>0||
|
||||
Number(s.longAnimationFrameCount??0)>0||
|
||||
Number(s.eventLoopGapCount??0)>0||
|
||||
Number(s.scriptHotspotCount??0)>0||
|
||||
(Array.isArray(perf.longTasks)&&perf.longTasks.length>0)||
|
||||
(Array.isArray(perf.longAnimationFrames)&&perf.longAnimationFrames.length>0)||
|
||||
(Array.isArray(perf.eventLoopGaps)&&perf.eventLoopGaps.length>0)||
|
||||
(Array.isArray(perf.scriptHotspots)&&perf.scriptHotspots.length>0);
|
||||
const commands=performanceCaptureCommandStatus(commandFiles);
|
||||
const runner=runnerStatusForPerformance();
|
||||
const pendingPerformanceCapture=commands.pendingCount>0||commands.processingCount>0;
|
||||
const cpuProfileStatus=hasCpuProfile?String(s.cpuProfileStatus||'captured'):pendingPerformanceCapture?'pending-command-no-cpu-profile':'no-cpu-profile';
|
||||
const attributionMode=hasCpuProfile?'cpu-profile-and-performance-observer':hasPerformanceObserverEvidence?'loaf-only-no-cpu-profile':'no-frontend-performance-evidence';
|
||||
const statement=hasCpuProfile
|
||||
? 'CPU profile artifacts are present; hotspot rows may be used as CPU profile evidence.'
|
||||
: pendingPerformanceCapture
|
||||
? 'LoAF-only / no CPU profile: performanceCapture is pending or processing, so CPU profile hotspots are unavailable for this run.'
|
||||
: 'LoAF-only / no CPU profile: no completed performanceCapture artifact is present, so do not cite CPU profile hotspots.';
|
||||
return {attributionMode,cpuProfileStatus,hasCpuProfile,noCpuProfile:!hasCpuProfile,loafOnly:!hasCpuProfile&&hasPerformanceObserverEvidence,pendingPerformanceCapture,runner,performanceCaptureCommands:commands,statement,valuesRedacted:true};
|
||||
}
|
||||
function performanceSummaryFromReport(){
|
||||
const perf=report.frontendPerformance&&typeof report.frontendPerformance==='object'?report.frontendPerformance:{};
|
||||
const summary=perf.summary&&typeof perf.summary==='object'?perf.summary:{};
|
||||
const findings=Array.isArray(report.findings)?report.findings.filter((item)=>String(item?.id||item?.kind||'').match(/^frontend-(?:long|event-loop|cpu-profile|performance)/u)).slice(0,6).map(compactPerfFinding):[];
|
||||
const captureRows=Array.isArray(perf.captures)?perf.captures:[];
|
||||
return {
|
||||
summary:{...summary, rawPerformanceRowCount:performanceRows.length, valuesRedacted:true},
|
||||
const commandFiles=readCommandFiles();
|
||||
const model={summary:{...summary, rawPerformanceRowCount:performanceRows.length, valuesRedacted:true},
|
||||
longTasks:Array.isArray(perf.longTasks)?perf.longTasks.slice(0,3).map(compactPerfEvent):[],
|
||||
longAnimationFrames:Array.isArray(perf.longAnimationFrames)?perf.longAnimationFrames.slice(0,3).map(compactPerfEvent):[],
|
||||
eventLoopGaps:Array.isArray(perf.eventLoopGaps)?perf.eventLoopGaps.slice(0,3).map(compactPerfEvent):[],
|
||||
@@ -761,16 +817,31 @@ function performanceSummaryFromReport(){
|
||||
profileStacks:Array.isArray(perf.profileStacks)?perf.profileStacks.slice(0,3).map(compactProfileStack):[],
|
||||
captures:captureRows.slice(-3).map(compactPerfCapture),
|
||||
findings,
|
||||
toolFindings:performanceToolFindings(),
|
||||
valuesRedacted:true
|
||||
};
|
||||
model.evidenceMode=performanceEvidenceMode(model,commandFiles);
|
||||
return {
|
||||
...model
|
||||
};
|
||||
}
|
||||
function renderPerformanceSummary(perf){
|
||||
const s=perf.summary||{};
|
||||
const mode=perf.evidenceMode||{};
|
||||
const captureCommands=mode.performanceCaptureCommands||{};
|
||||
const runner=mode.runner||{};
|
||||
const lines=['WEB-PROBE frontend performance '+(manifest.jobId||'-'),'======================================================='];
|
||||
lines.push('events='+String(s.eventCount??0)+' rawRows='+String(s.rawPerformanceRowCount??0)+' longTask='+String(s.longTaskCount??0)+' loaf='+String(s.longAnimationFrameCount??0)+' eventLoopGap='+String(s.eventLoopGapCount??0)+' captures='+String(s.captureCount??0));
|
||||
lines.push('max longTask='+String(s.maxLongTaskMs??'-')+'ms budget='+String(s.longTaskRedMs??'-')+'ms; max LoAF='+String(s.maxLongAnimationFrameMs??'-')+'ms budget='+String(s.longAnimationFrameRedMs??'-')+'ms; max gap='+String(s.maxEventLoopGapMs??'-')+'ms budget='+String(s.eventLoopGapRedMs??'-')+'ms');
|
||||
lines.push('evidence attribution='+String(mode.attributionMode||s.attributionMode||'-')+' cpuProfile='+String(mode.cpuProfileStatus||s.cpuProfileStatus||'-')+' pendingPerformanceCapture='+String(captureCommands.pendingCount??0)+' processingPerformanceCapture='+String(captureCommands.processingCount??0)+' runner='+String(runner.status||'-')+' runnerNotRunningOrFailed='+String(runner.notRunningOrFailed===true));
|
||||
if(mode.statement) lines.push('status: '+mode.statement);
|
||||
if(mode.pendingPerformanceCapture===true){
|
||||
const pending=[...(captureCommands.pending||[]),...(captureCommands.processing||[])].slice(0,4).map((item)=>String(item.id||'-')+'('+String(item.bucket||'-')+')').join(', ');
|
||||
lines.push('pending performanceCapture: '+(pending||'-'));
|
||||
}
|
||||
if(runner.notRunningOrFailed===true) lines.push('runner state: not-running/failed for performance evidence; treat missing CPU profile as tool state, not as proof that no CPU hotspot exists.');
|
||||
lines.push('','CPU profile hotspots');
|
||||
if(perf.profileHotspots.length===0) lines.push('-');
|
||||
if(perf.profileHotspots.length===0) lines.push(mode.noCpuProfile===true?'(no CPU profile hotspots; no completed performanceCapture artifact in this run)':'-');
|
||||
for(const item of perf.profileHotspots.slice(0,10)) lines.push(String(item.selfTimeMs??0)+'ms self '+short(item.functionName||'(anonymous)',44)+' '+short(item.url||item.scriptId||'-',92)+' line='+String(item.lineNumber??'-')+' captures='+String(item.captureCount??'-'));
|
||||
lines.push('','CPU profile stacks');
|
||||
if(perf.profileStacks.length===0) lines.push('-');
|
||||
@@ -786,6 +857,9 @@ function renderPerformanceSummary(perf){
|
||||
lines.push('','Findings');
|
||||
if(perf.findings.length===0) lines.push('-');
|
||||
for(const item of perf.findings.slice(0,12)) lines.push(String(item.severity||'-')+': '+String(item.id||item.kind||'-')+' count='+String(item.count??'-')+' '+short(item.summary||item.message||'',150));
|
||||
lines.push('','Tool status');
|
||||
if(!Array.isArray(perf.toolFindings)||perf.toolFindings.length===0) lines.push('-');
|
||||
for(const item of (perf.toolFindings||[]).slice(0,10)) lines.push(String(item.severity||'-')+': '+String(item.id||'-')+' count='+String(item.count??'-')+' '+short(item.summary||'',150));
|
||||
lines.push('','NEXT',' capture: bun scripts/cli.ts web-probe observe command '+(manifest.jobId||'<observer>')+' --type performanceCapture --duration-ms 5000 --wait-ms 8000',' analyze: bun scripts/cli.ts web-probe observe analyze '+(manifest.jobId||'<observer>'),'DISCLOSURE source=existing artifacts valuesRedacted=true; this view does not start browser/probe or mutate runtime.');
|
||||
return lines.join('\\n');
|
||||
}
|
||||
@@ -816,7 +890,7 @@ function renderProjectSummary(project){
|
||||
const rows=turnSummaryRows();
|
||||
if(view==='performance-summary'){
|
||||
const perf=performanceSummaryFromReport();
|
||||
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:perf.summary,artifactFileCount:files.length,skippedFileCount:skippedFiles.length,renderedText:renderPerformanceSummary(perf),sourceFiles:['performance-events.jsonl','artifacts.jsonl','analysis/report.json'],drillDown:'bun scripts/cli.ts web-probe observe collect '+String(manifest.jobId||'<observer>')+' --view files --file analysis/report.json',valuesRedacted:true}));
|
||||
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:perf.summary,evidenceMode:perf.evidenceMode,artifactFileCount:files.length,skippedFileCount:skippedFiles.length,renderedText:renderPerformanceSummary(perf),sourceFiles:['performance-events.jsonl','artifacts.jsonl','analysis/report.json','commands/pending/*.json','commands/processing/*.json','heartbeat.json','manifest.json'],drillDown:'bun scripts/cli.ts web-probe observe collect '+String(manifest.jobId||'<observer>')+' --view files --file analysis/report.json',valuesRedacted:true}));
|
||||
process.exit(0);
|
||||
}
|
||||
if(view==='project-summary'||view==='project-mdtodo-summary'){
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { test } from "bun:test";
|
||||
|
||||
import { nodeWebObserveCollectViewNodeScript } from "../hwlab-node-web-observe-collect";
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
test("performance-summary labels LoAF-only evidence when CPU profile capture is pending", async () => {
|
||||
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-performance-"));
|
||||
await mkdir(join(stateDir, "analysis"), { recursive: true });
|
||||
await mkdir(join(stateDir, "commands", "pending"), { recursive: true });
|
||||
await writeFile(join(stateDir, "manifest.json"), JSON.stringify({ jobId: "webobs-perf-test", status: "failed" }) + "\n");
|
||||
await writeFile(join(stateDir, "heartbeat.json"), JSON.stringify({ status: "failed", updatedAt: "2026-07-02T12:08:06Z" }) + "\n");
|
||||
await writeFile(join(stateDir, "performance-events.jsonl"), [
|
||||
JSON.stringify({
|
||||
type: "performance-event",
|
||||
ts: "2026-07-02T12:07:00Z",
|
||||
performance: { kind: "long-animation-frame", duration: 2083.8 },
|
||||
}),
|
||||
].join("\n") + "\n");
|
||||
await writeFile(join(stateDir, "commands", "pending", "cmd-perf.json"), JSON.stringify({
|
||||
id: "cmd-perf",
|
||||
type: "performanceCapture",
|
||||
createdAt: "2026-07-02T12:08:00Z",
|
||||
}) + "\n");
|
||||
await writeFile(join(stateDir, "analysis", "report.json"), JSON.stringify({
|
||||
manifest: { status: "failed" },
|
||||
heartbeat: { status: "failed", updatedAt: "2026-07-02T12:08:06Z" },
|
||||
frontendPerformance: {
|
||||
summary: {
|
||||
eventCount: 1,
|
||||
longTaskCount: 0,
|
||||
longAnimationFrameCount: 1,
|
||||
eventLoopGapCount: 0,
|
||||
captureCount: 0,
|
||||
scriptHotspotCount: 1,
|
||||
maxLongAnimationFrameMs: 2083.8,
|
||||
longAnimationFrameRedMs: 200,
|
||||
cpuProfileStatus: "missing",
|
||||
attributionMode: "loaf-only-no-cpu-profile",
|
||||
noCpuProfile: true,
|
||||
loafOnly: true,
|
||||
valuesRedacted: true,
|
||||
},
|
||||
scriptHotspots: [{
|
||||
sourceFunctionName: "Response.json.then",
|
||||
sourceURL: "https://hwlab.example.test/app.js",
|
||||
totalDurationMs: 2108,
|
||||
count: 2,
|
||||
valuesRedacted: true,
|
||||
}],
|
||||
profileHotspots: [],
|
||||
profileStacks: [],
|
||||
captures: [],
|
||||
longAnimationFrames: [{ ts: "2026-07-02T12:07:00Z", kind: "long-animation-frame", durationMs: 2083.8, sampleSeq: 42, pageRole: "control", scriptCount: 1 }],
|
||||
},
|
||||
findings: [{
|
||||
id: "frontend-performance-loaf-only-no-cpu-profile",
|
||||
severity: "amber",
|
||||
summary: "frontend performance evidence is LoAF/LongTask/event-loop only because no completed CPU profile capture is present",
|
||||
count: 1,
|
||||
}, {
|
||||
id: "tool-pending-commands-unconsumed",
|
||||
severity: "red",
|
||||
summary: "web-probe observe has pending/processing control commands that were not consumed by the runner",
|
||||
count: 1,
|
||||
}],
|
||||
}) + "\n");
|
||||
|
||||
const script = nodeWebObserveCollectViewNodeScript({
|
||||
maxFiles: 100,
|
||||
view: "performance-summary",
|
||||
traceId: null,
|
||||
sampleSeq: null,
|
||||
timestamp: null,
|
||||
turn: null,
|
||||
commandId: null,
|
||||
windowMs: null,
|
||||
});
|
||||
const result = spawnSync("bash", ["-lc", `state_dir=${shellQuote(stateDir)}\n${script}`], {
|
||||
cwd: join(import.meta.dir, "../../.."),
|
||||
encoding: "utf8",
|
||||
});
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||
|
||||
const output = JSON.parse(result.stdout);
|
||||
const text = String(output.renderedText ?? "");
|
||||
const debug = JSON.stringify({ summary: output.summary, evidenceMode: output.evidenceMode, text }, null, 2);
|
||||
assert.equal(output.evidenceMode.attributionMode, "loaf-only-no-cpu-profile", debug);
|
||||
assert.equal(output.evidenceMode.cpuProfileStatus, "pending-command-no-cpu-profile");
|
||||
assert.equal(output.evidenceMode.pendingPerformanceCapture, true);
|
||||
assert.match(text, /LoAF-only \/ no CPU profile/u);
|
||||
assert.match(text, /pending performanceCapture: cmd-perf\(pending\)/u);
|
||||
assert.match(text, /runner state: not-running\/failed/u);
|
||||
assert.match(text, /no CPU profile hotspots; no completed performanceCapture artifact/u);
|
||||
}, 20_000);
|
||||
Reference in New Issue
Block a user