Skip to content

Webui: adding profile argument and display in html report #3093

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 11, 2025
Merged
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: 5 additions & 0 deletions locust/argument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,11 @@ def setup_parser_arguments(parser):
dest="equal_weights",
help="Use equally distributed task weights, overriding the weights specified in the locustfile.",
)
other_group.add_argument(
"--profile",
type=str,
help="Set a profile to group the testruns together",
)

user_classes_group = parser.add_argument_group("User classes")
user_classes_group.add_argument(
Expand Down
3 changes: 3 additions & 0 deletions locust/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def __init__(
available_shape_classes: dict[str, LoadTestShape] | None = None,
available_user_tasks: dict[str, list[TaskSet | Callable]] | None = None,
dispatcher_class: type[UsersDispatcher] = UsersDispatcher,
profile: str | None = None,
):
self.runner: Runner | None = None
"""Reference to the :class:`Runner <locust.runners.Runner>` instance"""
Expand Down Expand Up @@ -76,6 +77,8 @@ def __init__(
"""Base URL of the target system"""
self.reset_stats = reset_stats
"""Determines if stats should be reset once all simulated users have been spawned"""
self.profile = profile
"""Profile name for the test run"""
if stop_timeout is not None:
self.stop_timeout = stop_timeout
elif parsed_options:
Expand Down
1 change: 1 addition & 0 deletions locust/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def get_html_report(
"locustfile": escape(str(environment.locustfile)),
"tasks": task_data,
"percentiles_to_chart": stats_module.PERCENTILES_TO_CHART,
"profile": escape(str(environment.profile)) if environment.profile else None,
},
theme=theme,
)
1 change: 1 addition & 0 deletions locust/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def create_environment(
available_user_classes=available_user_classes,
available_shape_classes=available_shape_classes,
available_user_tasks=available_user_tasks,
profile=options.profile,
)


Expand Down
23 changes: 23 additions & 0 deletions locust/test/test_load_locustfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,26 @@ def test_locustfile_from_url(self):
f"{os.getcwd()}/examples/basic.py",
)
)

def test_profile_flag(self):
options = parse_options()
self.assertEqual(None, options.profile)
options = parse_options(args=["--profile", "test-profile"])
self.assertEqual("test-profile", options.profile)
with temporary_file("profile=test-profile-from-file", suffix=".conf") as conf_file_path:
options = parse_options(
args=[
"--config",
conf_file_path,
]
)
self.assertEqual("test-profile-from-file", options.profile)
options = parse_options(
args=[
"--config",
conf_file_path,
"--profile",
"test-profile-from-arg",
]
)
self.assertEqual("test-profile-from-arg", options.profile)
1 change: 1 addition & 0 deletions locust/test/test_runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def __init__(self):
self.heartbeat_interval = 1
self.stop_timeout = 0.0
self.connection_broken = False
self.profile = None

def reset_stats(self):
pass
Expand Down
13 changes: 13 additions & 0 deletions locust/webui/src/pages/HtmlReport.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { Box, Typography, Container, Link } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
Expand Down Expand Up @@ -51,6 +52,12 @@ export default function HtmlReport({
responseTimeStatistics,
tasks,
}: IReport) {
useEffect(() => {
document.title = window.templateArgs.profile
? `Locust - ${window.templateArgs.profile}`
: 'Locust';
}, []);

return (
<Provider store={reportStore}>
<ThemeProvider theme={muiTheme}>
Expand All @@ -73,6 +80,12 @@ export default function HtmlReport({
)}
</Box>
<Box sx={{ my: 2 }}>
{window.templateArgs.profile && (
<Box sx={{ display: 'flex', columnGap: 0.5 }}>
<Typography fontWeight={600}>Profile:</Typography>
<Typography>{window.templateArgs.profile}</Typography>
</Box>
)}
<Box sx={{ display: 'flex', columnGap: 0.5 }}>
<Typography fontWeight={600}>During:</Typography>
<Typography>
Expand Down
25 changes: 24 additions & 1 deletion locust/webui/src/pages/tests/HtmlReport.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { describe, expect, test } from 'vitest';
import { describe, expect, test, beforeEach } from 'vitest';

import HtmlReport from 'pages/HtmlReport';
import { swarmReportMock } from 'test/mocks/swarmState.mock';
import { renderWithProvider } from 'test/testUtils';
import { IReportTemplateArgs } from 'types/swarm.types';
import { formatLocaleString } from 'utils/date';


describe('HtmlReport', () => {
beforeEach(() => {
// Reset window.templateArgs before each test
window.templateArgs = {} as IReportTemplateArgs;
document.title = '';
});

test('renders a report', () => {
const { getByRole, getByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);

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

test('profile is not rendered when it is not present', () => {
const { queryByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);

expect(queryByText('Profile:')).toBeNull();
});

test('profile is rendered when it is present', () => {
window.templateArgs.profile = 'test-profile';
const { getByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);

expect(getByText('Profile:')).toBeTruthy();
expect(getByText('test-profile')).toBeTruthy();
expect(document.title).toBe('Locust - test-profile');
});

test('formats the start and end time as expected', () => {
const { getByText } = renderWithProvider(<HtmlReport {...swarmReportMock} />);

Expand Down
2 changes: 2 additions & 0 deletions locust/webui/src/types/swarm.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface ISwarmState {
users: { [key: string]: ISwarmUser };
version: string;
workerCount: number;
profile?: string;
}

export interface IReport {
Expand All @@ -86,6 +87,7 @@ export interface IReportTemplateArgs extends Omit<IReport, 'charts'> {
isReport?: boolean;
percentilesToChart: number[];
percentilesToStatistics: number[];
profile?: string;
}

export interface ISwarmFormInput
Expand Down