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
29 changes: 25 additions & 4 deletions src/main/resources/wfc/schemas/report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,28 @@ properties:
type: array
items:
$ref: "#/$defs/TestCase"
executionTimeInSeconds:
type: integer
minimum: 0
description: "For how long, in seconds, the tool was running in total."
#OPTIONAL
extra:
description: "Extra, optional coverage information, collected by different tools."
type: [array, "null"]
items:
$ref: "#/$defs/Coverage"

required: ["schemaVersion","toolName","toolVersion","creationTime","faults","problemDetails","totalTests","testFilePaths","testCases"]
required:
- "schemaVersion"
- "toolName"
- "toolVersion"
- "creationTime"
- "faults"
- "problemDetails"
- "totalTests"
- "testFilePaths"
- "testCases"
- "executionTimeInSeconds"

$defs:
OperationId:
Expand Down Expand Up @@ -122,11 +136,18 @@ $defs:
RESTReport:
type: object
properties:
totalHttpCalls:
description: "Total number of HTTP calls made in all the test cases. A test case could contain several HTTP calls, \
outputHttpCalls:
description: "Total number of HTTP calls made in all the generated test cases given as output. \
A test case could contain several HTTP calls, \
e.g., a POST followed by a GET and then a DELETE."
type: integer
minimum: 0
evaluatedHttpCalls:
description: "Total number of all HTTP calls made during the whole fuzzing session. \
If the fuzzing was left running for hours, millions of HTTP could have been made.
The output generated tests will only contain a tiny subset of these evaluated calls."
type: integer
minimum: 0
endpointIds:
description: "Unique ids of all the endpoints in the tested API."
type: array
Expand All @@ -138,7 +159,7 @@ $defs:
type: array
items:
$ref: "#/$defs/CoveredEndpoint"
required: ["totalHttpCalls","endpointIds","coveredHttpStatus"]
required: ["outputHttpCalls","evaluatedHttpCalls","endpointIds","coveredHttpStatus"]

TestCase:
type: object
Expand Down
2 changes: 1 addition & 1 deletion web-report/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"copyRunFiles": "cpx webreport.bat '../target/classes/webreport' && cpx webreport.command '../target/classes/webreport' && cpx webreport.py '../target/classes/webreport' && cpx 'src-e2e/static/robots.txt' '../target/classes/webreport'",
"lint": "eslint .",
"preview": "vite preview",
"debug": "vite build && cpx 'src-e2e/static/*' '../target/classes/webreport' && vite preview",
"debug": "vite build && cpx src-e2e/static/* ../target/classes/webreport && vite preview",
"test": "vitest"
},
"dependencies": {
Expand Down
12 changes: 7 additions & 5 deletions web-report/src-e2e/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ describe('App test', () => {

it('handles successful data loading', async () => {
render(<App />);

// Initially shows loading state
expect(screen.getByText(/Please wait, files are loading.../)).toBeInTheDocument();

// Wait for loading to complete and verify header data
await waitFor(() => {
expect(screen.queryByText(/Please wait, files are loading.../)).toBeNull();
Expand Down Expand Up @@ -79,11 +79,13 @@ describe('App test', () => {
render(<App />);
expect(screen.getByText(/Please wait, files are loading.../)).toBeInTheDocument();
const total = reportData.problemDetails.rest.endpointIds.length;
const totalHttpCalls = reportData.problemDetails.rest.totalHttpCalls;
const outputHttpCalls = reportData.problemDetails.rest.outputHttpCalls;
const evaluatedHttpCalls = reportData.problemDetails.rest.evaluatedHttpCalls;

await waitFor(() => {
expect(screen.getByTestId('rest-report-endpoint')).toContainHTML(`${total}`);
expect(screen.getByTestId('rest-report-http-calls')).toContainHTML(`${totalHttpCalls}`);
expect(screen.getByTestId('rest-report-output-http-calls')).toContainHTML(`${outputHttpCalls}`);
expect(screen.getByTestId('rest-report-evaluated-http-calls')).toContainHTML(`${evaluatedHttpCalls}`);
});

});
Expand Down Expand Up @@ -143,4 +145,4 @@ describe('App test', () => {
expect(screen.getByTestId('faults-component-fault-counts')).toContainHTML(faultCounts.length.toString());
});
});
});
});
8 changes: 5 additions & 3 deletions web-report/src-e2e/static/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"toolName": "EvoMaster",
"toolVersion": "unknown",
"creationTime": "2025-04-09T19:31:54.258Z",
"executionTimeInSeconds": 6780000,
"faults": {
"totalNumber": 529,
"foundFaults": [
Expand Down Expand Up @@ -2970,7 +2971,7 @@
},
"problemDetails": {
"rest": {
"totalHttpCalls": 130,
"outputHttpCalls": 25,
"endpointIds": [
"GET:/app/api/assignments",
"POST:/app/api/assignments",
Expand Down Expand Up @@ -3951,7 +3952,8 @@
200
]
}
]
],
"evaluatedHttpCalls": 132
}
},
"totalTests": 127,
Expand Down Expand Up @@ -4867,4 +4869,4 @@
]
}
]
}
}
6 changes: 4 additions & 2 deletions web-report/src/assets/info.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"numberOfEndpoints": "Number of endpoints (verb:path) in the API.",
"numberOfHttpCalls": "Total number of HTTP calls in the generated test suites.",
"outputHttpCalls": "Total number of HTTP calls contained in all generated test cases - a single test case may include multiple calls (e.g., a POST followed by a GET and then a DELETE).",
"evaluatedHttpCalls": "Total number of all HTTP calls made during the entire fuzzing session - in long runs, this can reach millions, while the generated tests include only a small subset of these calls.",
"executionTimeInSeconds": "For how long, in seconds, the tool was running in total.",
"codeNumberIdentifiers": "Code number identifiers for detected fault types",
"identifierName": "Identifier name for the fault type.",
"testFilesLocated": "{numberOfTestCases} test cases are located in {fileName}",
Expand All @@ -15,4 +17,4 @@
"creationDate": "Date when the report was generated.",
"toolNameVersion": "Name and version of the tool that generated the report.",
"schemaVersion": "Version of the schema used for the report."
}
}
4 changes: 3 additions & 1 deletion web-report/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ export const Dashboard: React.FC = () => {
<Overview rest={data.problemDetails.rest}
testCases={data.testCases}
testFiles={numberOfTestCaseOfFiles}
faults={data.faults}/>
faults={data.faults}
executionTimeInSeconds={data.executionTimeInSeconds}
/>
</TabsContent>

<TabsContent value="endpoints">
Expand Down
158 changes: 126 additions & 32 deletions web-report/src/components/GeneratedTests.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Card} from "@/components/ui/card.tsx";
import {Target} from "lucide-react";
import {Target, Clock, FileText, TestTube, Network, CheckCircle, FolderOpen} from "lucide-react";
import type React from "react";
import {getFileColor, getText} from "@/lib/utils";
import info from "@/assets/info.json";
Expand All @@ -11,57 +11,151 @@ interface IGeneratedTests {
fileName: string,
numberOfTestCases: number
}>
totalHttpCalls?: number
outputHttpCalls?: number
evaluatedHttpCalls?: number
executionTimeInSeconds?: number
}

export const GeneratedTests: React.FC<IGeneratedTests> = ({totalTests, testFiles, totalHttpCalls}) => (
const formatExecutionTime = (totalSeconds?: number) => {
if (totalSeconds === undefined || totalSeconds === null) return null;

const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);

return { days, hours, minutes, seconds };
};

export const GeneratedTests: React.FC<IGeneratedTests> = ({totalTests, testFiles, outputHttpCalls, evaluatedHttpCalls, executionTimeInSeconds}) => {
const timeBreakdown = formatExecutionTime(executionTimeInSeconds);

return (
<Card className="border-2 border-black p-6 rounded-none">
<div className="flex items-start gap-4">
<Target className="w-6 h-6 text-gray-500"/>
<div className="flex-1">
<div className="flex justify-between">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<ReportTooltip tooltipText={info.generatedTestFiles}>
<span className="text-lg font-bold"># Generated Test Files:</span>
<div className="bg-gradient-to-br from-green-50 to-emerald-50 border-2 border-green-300 rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="bg-green-500 rounded-full p-2">
<FileText className="w-5 h-5 text-white"/>
</div>
<div className="flex-1">
<div className="text-sm font-medium text-gray-600">Generated Test Files</div>
<div className="text-2xl font-bold text-green-700" data-testid="generated-tests-total-test-files">{testFiles.length}</div>
</div>
</div>
</div>
</ReportTooltip>
<span className="text-lg font-bold" data-testid="generated-tests-total-test-files">{testFiles.length}</span>
</div>
<div className="flex justify-between">

<ReportTooltip tooltipText={info.generatedTestCases}>
<span className="text-lg font-bold"># Generated Tests Cases:</span>
<div className="bg-gradient-to-br from-orange-50 to-amber-50 border-2 border-orange-300 rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="bg-orange-500 rounded-full p-2">
<TestTube className="w-5 h-5 text-white"/>
</div>
<div className="flex-1">
<div className="text-sm font-medium text-gray-600">Generated Test Cases</div>
<div className="text-2xl font-bold text-orange-700" data-testid="generated-tests-total-tests">{totalTests}</div>
</div>
</div>
</div>
</ReportTooltip>
<span className="text-lg font-bold" data-testid="generated-tests-total-tests">{totalTests}</span>
</div>
<div className="flex justify-between">
<ReportTooltip tooltipText={info.numberOfHttpCalls}>
<span className="text-lg font-bold"># HTTP Calls:</span>

<ReportTooltip tooltipText={info.outputHttpCalls}>
<div className="bg-gradient-to-br from-cyan-50 to-sky-50 border-2 border-cyan-300 rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="bg-cyan-500 rounded-full p-2">
<Network className="w-5 h-5 text-white"/>
</div>
<div className="flex-1">
<div className="text-sm font-medium text-gray-600">Output HTTP Calls</div>
<div className="text-2xl font-bold text-cyan-700" data-testid="rest-report-output-http-calls">{outputHttpCalls}</div>
</div>
</div>
</div>
</ReportTooltip>

<ReportTooltip tooltipText={info.evaluatedHttpCalls}>
<div className="bg-gradient-to-br from-pink-50 to-rose-50 border-2 border-pink-300 rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="bg-pink-500 rounded-full p-2">
<CheckCircle className="w-5 h-5 text-white"/>
</div>
<div className="flex-1">
<div className="text-sm font-medium text-gray-600">Evaluated HTTP Calls</div>
<div className="text-2xl font-bold text-pink-700" data-testid="rest-report-evaluated-http-calls">{evaluatedHttpCalls}</div>
</div>
</div>
</div>
</ReportTooltip>
<span className="text-lg font-bold" data-testid="rest-report-http-calls">{totalHttpCalls}</span>
</div>
{timeBreakdown && (
<div className="mt-4 p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-2 border-blue-300 rounded-lg">
<ReportTooltip tooltipText={info.executionTimeInSeconds}>
<div className="flex items-center gap-2 mb-3">
<Clock className="w-5 h-5 text-blue-600"/>
<span className="text-base font-bold text-blue-900">Execution Time</span>
</div>
</ReportTooltip>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3" data-testid="rest-report-execution-time">
<div className="text-center bg-white rounded-lg p-3 border border-blue-200 shadow-sm">
<div className="text-2xl font-bold text-blue-600">{timeBreakdown.days}</div>
<div className="text-xs font-medium text-gray-600 mt-1">Days</div>
</div>
<div className="text-center bg-white rounded-lg p-3 border border-blue-200 shadow-sm">
<div className="text-2xl font-bold text-indigo-600">{timeBreakdown.hours}</div>
<div className="text-xs font-medium text-gray-600 mt-1">Hours</div>
</div>
<div className="text-center bg-white rounded-lg p-3 border border-blue-200 shadow-sm">
<div className="text-2xl font-bold text-purple-600">{timeBreakdown.minutes}</div>
<div className="text-xs font-medium text-gray-600 mt-1">Minutes</div>
</div>
<div className="text-center bg-white rounded-lg p-3 border border-blue-200 shadow-sm">
<div className="text-2xl font-bold text-violet-600">{timeBreakdown.seconds}</div>
<div className="text-xs font-medium text-gray-600 mt-1">Seconds</div>
</div>
</div>
</div>
)}

<div className="mt-4 pt-4 border-t border-gray-200">
<div className="text-sm font-medium text-gray-700 mb-2">Test Files</div>
<div className="space-y-1">
{
testFiles.length > 0 ? (
testFiles.map((file, index) => (
<div className="flex items-center gap-2 text-sm text-gray-600" key={index}>
<div className={`w-2 h-2 ${getFileColor(index, file.fileName)} rounded-full`}></div>
<ReportTooltip tooltipText={getText(info.testFilesLocated,
<div className="mt-4">
<div className="bg-gradient-to-r from-slate-50 to-gray-50 border-2 border-slate-300 rounded-lg p-4">
<div className="flex items-center gap-2 mb-3">
<FolderOpen className="w-5 h-5 text-slate-600"/>
<span className="text-base font-bold text-slate-900">Test Files</span>
</div>
<div className="space-y-2">
{
testFiles.length > 0 ? (
testFiles.map((file, index) => (
<ReportTooltip key={index} tooltipText={getText(info.testFilesLocated,
{
fileName: file.fileName,
numberOfTestCases: file.numberOfTestCases
})}>
<span>{file.fileName} (# {file.numberOfTestCases})</span>
<div className="flex items-center gap-3 bg-white rounded-md p-3 border border-slate-200 hover:border-slate-400 hover:shadow-sm transition-all">
<div className={`w-3 h-3 ${getFileColor(index, file.fileName)} rounded-full flex-shrink-0`}></div>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium text-gray-700 truncate block">{file.fileName}</span>
</div>
<div className="flex-shrink-0 bg-slate-100 px-2 py-1 rounded-md">
<span className="text-xs font-bold text-slate-700">{file.numberOfTestCases}</span>
</div>
</div>
</ReportTooltip>
</div>
))
) : (
<div className="text-gray-500 italic">No test files generated.</div>
)
}
))
) : (
<div className="text-center text-gray-500 italic py-4">No test files generated.</div>
)
}
</div>
</div>
</div>
</div>
</div>
</Card>
)
);
};
11 changes: 8 additions & 3 deletions web-report/src/pages/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ interface IOverviewType {
numberOfTestCases: number
}>,
faults: Faults
executionTimeInSeconds: number;
}

export const Overview: React.FC<IOverviewType> = ({rest, testCases, testFiles, faults}) => {
export const Overview: React.FC<IOverviewType> = ({rest, testCases, testFiles, faults, executionTimeInSeconds}) => {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Left Panel */}
{rest ? <RestReports {...rest}/> : <div>Please provide rest report.</div>}
{/* Right Panel */}
<div className="flex flex-col gap-6">
{/* Generated Tests */}
<GeneratedTests totalTests={testCases.length} testFiles={testFiles} totalHttpCalls={rest?.totalHttpCalls}/>
<GeneratedTests totalTests={testCases.length} testFiles={testFiles}
outputHttpCalls={rest?.outputHttpCalls}
evaluatedHttpCalls={rest?.evaluatedHttpCalls}
executionTimeInSeconds={executionTimeInSeconds}
/>
{/* Faults */}
<FaultsComponent {...faults}/>
</div>
</div>
)
}
}
Loading