Skip to content

Commit 0f80721

Browse files
authored
Merge pull request #3093 from schwannden/feature/add-profile-config
Webui: adding profile argument and display in html report
2 parents 4d966fc + c3c836f commit 0f80721

File tree

9 files changed

+73
-1
lines changed

9 files changed

+73
-1
lines changed

Diff for: locust/argument_parser.py

+5
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,11 @@ def setup_parser_arguments(parser):
838838
dest="equal_weights",
839839
help="Use equally distributed task weights, overriding the weights specified in the locustfile.",
840840
)
841+
other_group.add_argument(
842+
"--profile",
843+
type=str,
844+
help="Set a profile to group the testruns together",
845+
)
841846

842847
user_classes_group = parser.add_argument_group("User classes")
843848
user_classes_group.add_argument(

Diff for: locust/env.py

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
available_shape_classes: dict[str, LoadTestShape] | None = None,
4040
available_user_tasks: dict[str, list[TaskSet | Callable]] | None = None,
4141
dispatcher_class: type[UsersDispatcher] = UsersDispatcher,
42+
profile: str | None = None,
4243
):
4344
self.runner: Runner | None = None
4445
"""Reference to the :class:`Runner <locust.runners.Runner>` instance"""
@@ -76,6 +77,8 @@ def __init__(
7677
"""Base URL of the target system"""
7778
self.reset_stats = reset_stats
7879
"""Determines if stats should be reset once all simulated users have been spawned"""
80+
self.profile = profile
81+
"""Profile name for the test run"""
7982
if stop_timeout is not None:
8083
self.stop_timeout = stop_timeout
8184
elif parsed_options:

Diff for: locust/html.py

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def get_html_report(
105105
"locustfile": escape(str(environment.locustfile)),
106106
"tasks": task_data,
107107
"percentiles_to_chart": stats_module.PERCENTILES_TO_CHART,
108+
"profile": escape(str(environment.profile)) if environment.profile else None,
108109
},
109110
theme=theme,
110111
)

Diff for: locust/main.py

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def create_environment(
8989
available_user_classes=available_user_classes,
9090
available_shape_classes=available_shape_classes,
9191
available_user_tasks=available_user_tasks,
92+
profile=options.profile,
9293
)
9394

9495

Diff for: locust/test/test_load_locustfile.py

+23
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,26 @@ def test_locustfile_from_url(self):
225225
f"{os.getcwd()}/examples/basic.py",
226226
)
227227
)
228+
229+
def test_profile_flag(self):
230+
options = parse_options()
231+
self.assertEqual(None, options.profile)
232+
options = parse_options(args=["--profile", "test-profile"])
233+
self.assertEqual("test-profile", options.profile)
234+
with temporary_file("profile=test-profile-from-file", suffix=".conf") as conf_file_path:
235+
options = parse_options(
236+
args=[
237+
"--config",
238+
conf_file_path,
239+
]
240+
)
241+
self.assertEqual("test-profile-from-file", options.profile)
242+
options = parse_options(
243+
args=[
244+
"--config",
245+
conf_file_path,
246+
"--profile",
247+
"test-profile-from-arg",
248+
]
249+
)
250+
self.assertEqual("test-profile-from-arg", options.profile)

Diff for: locust/test/test_runners.py

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def __init__(self):
116116
self.heartbeat_interval = 1
117117
self.stop_timeout = 0.0
118118
self.connection_broken = False
119+
self.profile = None
119120

120121
def reset_stats(self):
121122
pass

Diff for: locust/webui/src/pages/HtmlReport.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect } from 'react';
12
import { Box, Typography, Container, Link } from '@mui/material';
23
import CssBaseline from '@mui/material/CssBaseline';
34
import { ThemeProvider } from '@mui/material/styles';
@@ -51,6 +52,12 @@ export default function HtmlReport({
5152
responseTimeStatistics,
5253
tasks,
5354
}: IReport) {
55+
useEffect(() => {
56+
document.title = window.templateArgs.profile
57+
? `Locust - ${window.templateArgs.profile}`
58+
: 'Locust';
59+
}, []);
60+
5461
return (
5562
<Provider store={reportStore}>
5663
<ThemeProvider theme={muiTheme}>
@@ -73,6 +80,12 @@ export default function HtmlReport({
7380
)}
7481
</Box>
7582
<Box sx={{ my: 2 }}>
83+
{window.templateArgs.profile && (
84+
<Box sx={{ display: 'flex', columnGap: 0.5 }}>
85+
<Typography fontWeight={600}>Profile:</Typography>
86+
<Typography>{window.templateArgs.profile}</Typography>
87+
</Box>
88+
)}
7689
<Box sx={{ display: 'flex', columnGap: 0.5 }}>
7790
<Typography fontWeight={600}>During:</Typography>
7891
<Typography>

Diff for: locust/webui/src/pages/tests/HtmlReport.test.tsx

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import { describe, expect, test } from 'vitest';
1+
import { describe, expect, test, beforeEach } from 'vitest';
22

33
import HtmlReport from 'pages/HtmlReport';
44
import { swarmReportMock } from 'test/mocks/swarmState.mock';
55
import { renderWithProvider } from 'test/testUtils';
6+
import { IReportTemplateArgs } from 'types/swarm.types';
67
import { formatLocaleString } from 'utils/date';
78

9+
810
describe('HtmlReport', () => {
11+
beforeEach(() => {
12+
// Reset window.templateArgs before each test
13+
window.templateArgs = {} as IReportTemplateArgs;
14+
document.title = '';
15+
});
16+
917
test('renders a report', () => {
1018
const { getByRole, getByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);
1119

@@ -19,6 +27,21 @@ describe('HtmlReport', () => {
1927
expect(getByText(swarmReportMock.host)).toBeTruthy();
2028
});
2129

30+
test('profile is not rendered when it is not present', () => {
31+
const { queryByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);
32+
33+
expect(queryByText('Profile:')).toBeNull();
34+
});
35+
36+
test('profile is rendered when it is present', () => {
37+
window.templateArgs.profile = 'test-profile';
38+
const { getByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);
39+
40+
expect(getByText('Profile:')).toBeTruthy();
41+
expect(getByText('test-profile')).toBeTruthy();
42+
expect(document.title).toBe('Locust - test-profile');
43+
});
44+
2245
test('formats the start and end time as expected', () => {
2346
const { getByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);
2447

Diff for: locust/webui/src/types/swarm.types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface ISwarmState {
6464
users: { [key: string]: ISwarmUser };
6565
version: string;
6666
workerCount: number;
67+
profile?: string;
6768
}
6869

6970
export interface IReport {
@@ -86,6 +87,7 @@ export interface IReportTemplateArgs extends Omit<IReport, 'charts'> {
8687
isReport?: boolean;
8788
percentilesToChart: number[];
8889
percentilesToStatistics: number[];
90+
profile?: string;
8991
}
9092

9193
export interface ISwarmFormInput

0 commit comments

Comments
 (0)