├── Abstracta.JmeterDsl.Tests
├── Usings.cs
├── Core
│ ├── Configs
│ │ ├── data.csv
│ │ └── DslCsvDataSetTest.cs
│ ├── Controllers
│ │ ├── DslTransactionControllerTest.cs
│ │ ├── ForLoopControllerTest.cs
│ │ └── DslSimpleControllerTest.cs
│ ├── Timers
│ │ ├── DslConstantTimerTest.cs
│ │ └── DslUniformRandomTimerTest.cs
│ ├── Samplers
│ │ └── DslFlowControlActionTest.cs
│ ├── PreProcessors
│ │ └── DslJsr223PreProcessorTest.cs
│ ├── Listeners
│ │ ├── ResponseFileSaverTest.cs
│ │ └── JtlWriterTest.cs
│ ├── ThreadGroups
│ │ └── DslThreadGroupTest.cs
│ ├── Assertions
│ │ └── DslResponseAssertionTest.cs
│ └── PostProcessors
│ │ └── DslRegexExtractorTest.cs
├── Http
│ └── sample.xml
├── log4j2.xml
├── Abstracta.JmeterDsl.Tests.csproj
└── WireMockAssertionsExtensions.cs
├── Abstracta.JmeterDsl.Azure.Tests
├── Usings.cs
├── log4j2.xml
├── Abstracta.JmeterDsl.Azure.Tests.csproj
└── AzureEngineTest.cs
├── Abstracta.JmeterDsl.BlazeMeter.Tests
├── Usings.cs
├── log4j2.xml
├── Abstracta.JmeterDsl.BlazeMeter.Tests.csproj
└── BlazeMeterEngineTests.cs
├── logo.png
├── docs
├── guide
│ ├── protocols
│ │ ├── index.md
│ │ └── http
│ │ │ ├── redirects.md
│ │ │ ├── cookies-and-cache.md
│ │ │ ├── index.md
│ │ │ ├── methods-and-body.md
│ │ │ ├── multipart.md
│ │ │ ├── headers.md
│ │ │ └── parameters.md
│ ├── request-generation
│ │ ├── loops
│ │ │ ├── index.md
│ │ │ └── forloop-controller.md
│ │ ├── timers
│ │ │ ├── index.md
│ │ │ ├── synchronized-samples.png
│ │ │ ├── not-synchronized-samples.png
│ │ │ ├── synchronizing-timer.md
│ │ │ └── constant-and-random.md
│ │ ├── index.md
│ │ ├── jsr223-pre-processor.md
│ │ ├── csv-dataset.md
│ │ └── group-requests.md
│ ├── scale
│ │ ├── azure.png
│ │ ├── blazemeter.png
│ │ ├── index.md
│ │ ├── azure.md
│ │ └── blazemeter.md
│ ├── debugging
│ │ ├── images
│ │ │ ├── test-plan-gui.png
│ │ │ ├── view-results-tree.png
│ │ │ ├── intellij-remote-jvm-debug.png
│ │ │ └── jmeter-http-sampler-debugging.png
│ │ ├── index.md
│ │ ├── show-in-gui.md
│ │ ├── debug-jmeter.md
│ │ ├── dummy-sampler.md
│ │ └── view-results-tree.md
│ ├── response-processing
│ │ ├── index.md
│ │ ├── correlation
│ │ │ ├── index.md
│ │ │ └── regex-extractor.md
│ │ └── response-assertion.md
│ ├── thread-groups
│ │ ├── images
│ │ │ ├── ultimate-thread-group-gui.png
│ │ │ └── ultimate-thread-group-timeline.png
│ │ ├── index.md
│ │ └── ramps-and-holds.md
│ ├── reporting
│ │ ├── index.md
│ │ └── logging.md
│ ├── setup.md
│ ├── index.md
│ └── simple-test-plan.md
├── support
│ ├── azure-logo.png
│ ├── octoperf-logo.png
│ ├── abstracta-logo.png
│ ├── blazemeter-logo.png
│ └── index.md
├── .vuepress
│ ├── public
│ │ └── favicon.ico
│ ├── plugins
│ │ ├── repoLinkSolverPlugin.ts
│ │ └── includedRelativeLinkSolverPlugin.ts
│ ├── client.ts
│ ├── components
│ │ ├── HomeFeatures.vue
│ │ ├── NavbarBrand.vue
│ │ ├── HomeHero.vue
│ │ └── AutoLink.vue
│ ├── styles
│ │ └── index.scss
│ └── config.ts
├── package.json
└── index.md
├── .github
├── next-minor-alpha.sh
├── nuget-deploy.sh
├── fix-docs-version.sh
├── vuepress-deploy.sh
├── update-sample-version.sh
├── semver-check.sh
├── fix-project-version.sh
└── workflows
│ ├── build.yml
│ └── release.yml
├── .envrc
├── Abstracta.JmeterDsl
├── Core
│ ├── Bridge
│ │ ├── YamlTypeAttribute.cs
│ │ ├── TestPlanExecution.cs
│ │ ├── IDslProperty.cs
│ │ ├── JvmException.cs
│ │ ├── EnumConverter.cs
│ │ ├── TimeSpanConverter.cs
│ │ └── BridgedObjectConverter.cs
│ ├── Engines
│ │ ├── EmbeddedJmeterEngine.cs
│ │ └── BaseJmeterEngine.cs
│ ├── Samplers
│ │ ├── ISamplerChild.cs
│ │ ├── DslFlowControlAction.cs
│ │ ├── BaseSampler.cs
│ │ └── DslDummySampler.cs
│ ├── Listeners
│ │ ├── BaseListener.cs
│ │ ├── ResponseFileSaver.cs
│ │ └── ResultsTreeVisualizer.cs
│ ├── ThreadGroups
│ │ ├── IThreadGroupChild.cs
│ │ └── BaseThreadGroup.cs
│ ├── ITestPlanChild.cs
│ ├── Timers
│ │ ├── BaseTimer.cs
│ │ ├── DslSynchronizingTimer.cs
│ │ ├── DslConstantTimer.cs
│ │ └── DslUniformRandomTimer.cs
│ ├── Configs
│ │ └── BaseConfigElement.cs
│ ├── IDslTestElement.cs
│ ├── TestElements
│ │ ├── IMultiLevelTestElement.cs
│ │ ├── BaseTestElement.cs
│ │ ├── DslJsr223TestElement.cs
│ │ ├── TestElementContainer.cs
│ │ └── DslScopedTestElement.cs
│ ├── Stats
│ │ ├── CountMetricSummary.cs
│ │ ├── TimeMetricSummary.cs
│ │ └── StatsSummary.cs
│ ├── IDslJmeterEngine.cs
│ ├── PostProcessors
│ │ ├── DslVariableExtractor.cs
│ │ └── DslRegexExtractor.cs
│ ├── Controllers
│ │ ├── ForLoopController.cs
│ │ ├── DslSimpleController.cs
│ │ ├── BaseController.cs
│ │ └── DslTransactionController.cs
│ ├── TestPlanStats.cs
│ ├── PreProcessors
│ │ └── DslJsr223PreProcessor.cs
│ └── DslTestPlan.cs
├── pom.xml
├── Http
│ ├── DslHttpCache.cs
│ ├── DslHttpCookies.cs
│ └── HttpHeaders.cs
└── Abstracta.JmeterDsl.csproj
├── .editorconfig
├── devbox.json
├── pom.xml
├── Abstracta.JmeterDsl.Azure
├── pom.xml
└── Abstracta.JmeterDsl.Azure.csproj
├── devbox.lock
├── Abstracta.JmeterDsl.BlazeMeter
├── pom.xml
├── Abstracta.JmeterDsl.BlazeMeter.csproj
└── BlazeMeterLocation.cs
├── Abstracta.JmeterDsl.sln
└── README.md
/Abstracta.JmeterDsl.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Azure.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.BlazeMeter.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/logo.png
--------------------------------------------------------------------------------
/docs/guide/protocols/index.md:
--------------------------------------------------------------------------------
1 | ## Protocols
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Configs/data.csv:
--------------------------------------------------------------------------------
1 | VAR1,VAR2
2 | val1,"val2"
3 | "val,3",val4
4 |
--------------------------------------------------------------------------------
/docs/guide/request-generation/loops/index.md:
--------------------------------------------------------------------------------
1 | ### Loops
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/guide/scale/azure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/scale/azure.png
--------------------------------------------------------------------------------
/docs/support/azure-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/support/azure-logo.png
--------------------------------------------------------------------------------
/docs/support/octoperf-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/support/octoperf-logo.png
--------------------------------------------------------------------------------
/docs/guide/scale/blazemeter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/scale/blazemeter.png
--------------------------------------------------------------------------------
/docs/support/abstracta-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/support/abstracta-logo.png
--------------------------------------------------------------------------------
/docs/support/blazemeter-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/support/blazemeter-logo.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/docs/guide/debugging/images/test-plan-gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/debugging/images/test-plan-gui.png
--------------------------------------------------------------------------------
/docs/guide/request-generation/timers/index.md:
--------------------------------------------------------------------------------
1 | ### Timers
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/guide/debugging/images/view-results-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/debugging/images/view-results-tree.png
--------------------------------------------------------------------------------
/docs/guide/response-processing/index.md:
--------------------------------------------------------------------------------
1 | ## Response processing
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/guide/debugging/images/intellij-remote-jvm-debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/debugging/images/intellij-remote-jvm-debug.png
--------------------------------------------------------------------------------
/docs/guide/debugging/images/jmeter-http-sampler-debugging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/debugging/images/jmeter-http-sampler-debugging.png
--------------------------------------------------------------------------------
/docs/guide/request-generation/timers/synchronized-samples.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/request-generation/timers/synchronized-samples.png
--------------------------------------------------------------------------------
/docs/guide/thread-groups/images/ultimate-thread-group-gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/thread-groups/images/ultimate-thread-group-gui.png
--------------------------------------------------------------------------------
/docs/guide/request-generation/timers/not-synchronized-samples.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/request-generation/timers/not-synchronized-samples.png
--------------------------------------------------------------------------------
/docs/guide/thread-groups/images/ultimate-thread-group-timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abstracta/jmeter-dotnet-dsl/HEAD/docs/guide/thread-groups/images/ultimate-thread-group-timeline.png
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Http/sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Tested
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/next-minor-alpha.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script solves next minor version
4 | set -eo pipefail
5 |
6 | VERSION="$1"
7 | MAJOR="${VERSION%%.*}"
8 | VERSION="${VERSION#*.}"
9 | MINOR="${VERSION%%.*}"
10 | echo "${MAJOR}.$((MINOR + 1))-alpha1"
11 |
--------------------------------------------------------------------------------
/docs/guide/request-generation/index.md:
--------------------------------------------------------------------------------
1 | ## Requests generation
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | # Automatically sets up your devbox environment whenever you cd into this
2 | # directory via our direnv integration:
3 |
4 | eval "$(devbox generate direnv --print-envrc)"
5 |
6 | # check out https://www.jetpack.io/devbox/docs/ide_configuration/direnv/
7 | # for more details
8 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Bridge/YamlTypeAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Bridge
4 | {
5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
6 | public class YamlTypeAttribute : Attribute
7 | {
8 | public string TagName { get; set; }
9 | }
10 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Engines/EmbeddedJmeterEngine.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Engines
2 | {
3 | ///
4 | /// Allows running test plans in an embedded JMeter instance.
5 | ///
6 | public class EmbeddedJmeterEngine : BaseJmeterEngine
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Samplers/ISamplerChild.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Samplers
2 | {
3 | ///
4 | /// Test elements which can be nested as children of a sampler in JMeter, should implement this interface.
5 | ///
6 | public interface ISamplerChild : IDslTestElement
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/redirects.md:
--------------------------------------------------------------------------------
1 | #### Redirects
2 |
3 | When JMeter DSL (using JMeter logic) detects a redirection, it will automatically do a request to the redirected URL and register the redirection as a sub-sample of the main request.
4 |
5 | If you want to disable such logic, you can just call `.FollowRedirects(false)` in a given `HttpSampler`.
6 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Listeners/BaseListener.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Listeners
4 | {
5 | public abstract class BaseListener : BaseTestElement, IMultiLevelTestElement
6 | {
7 | protected BaseListener()
8 | : base(null)
9 | {
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/ThreadGroups/IThreadGroupChild.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.ThreadGroups
2 | {
3 | ///
4 | /// Test elements that can be added as direct children of a thread group in jmeter should implement this interface.
5 | ///
6 | public interface IThreadGroupChild : IDslTestElement
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/docs/guide/response-processing/correlation/index.md:
--------------------------------------------------------------------------------
1 | ### Use part of a response in a subsequent request (aka correlation)
2 |
3 | It is a usual requirement while creating a test plan for an application to be able to use part of a response (e.g.: a generated ID, token, etc.) in a subsequent request. This can be easily achieved using JMeter extractors and variables.
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/ITestPlanChild.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core
2 | {
3 | ///
4 | /// Test elements that can be added directly as test plan children in JMeter should implement this interface.
5 | /// Check for an example.
6 | ///
7 | public interface ITestPlanChild : IDslTestElement
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/docs/.vuepress/plugins/repoLinkSolverPlugin.ts:
--------------------------------------------------------------------------------
1 | import mditPlugin from 'markdown-it-replace-link'
2 |
3 | export const repoLinkSolverPlugin = ({repoUrl} : {repoUrl: String}) => ({
4 | name: 'repoLinkSolverPlugin',
5 | extendsMarkdown: (md) => {
6 | md.use(mditPlugin, {
7 | replaceLink: (link, env) => link.startsWith('/') ? repoUrl + '/tree/master' + link : link
8 | })
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Timers/BaseTimer.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Timers
4 | {
5 | ///
6 | /// Contains common logic for all timers.
7 | ///
8 | public abstract class BaseTimer : BaseTestElement, IMultiLevelTestElement
9 | {
10 | public BaseTimer(string name)
11 | : base(name)
12 | {
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/nuget-deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script takes care of deploying packages to nuget
4 | #
5 | # Required environment variables: NUGET_API_KEY
6 |
7 | set -xeo pipefail
8 |
9 | push_package() {
10 | dotnet nuget push "$1" --api-key "${NUGET_API_KEY}" --source https://api.nuget.org/v3/index.json --skip-duplicate
11 | }
12 |
13 | find . -name "*.nupkg" -or -name "*.snupkg" | sort | while read PACKAGE; do
14 | push_package ${PACKAGE}
15 | done
16 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Bridge/TestPlanExecution.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Bridge
2 | {
3 | public class TestPlanExecution
4 | {
5 | private readonly IDslJmeterEngine _engine;
6 | private readonly DslTestPlan _testPlan;
7 |
8 | public TestPlanExecution(IDslJmeterEngine engine, DslTestPlan testPlan)
9 | {
10 | _engine = engine;
11 | _testPlan = testPlan;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Timers/DslSynchronizingTimer.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Timers
2 | {
3 | ///
4 | /// Uses JMeter Synchronizing Timer to allow sending a batch of requests simultaneously to a system
5 | /// under test.
6 | ///
7 | public class DslSynchronizingTimer : BaseTimer
8 | {
9 | public DslSynchronizingTimer()
10 | : base(null)
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/docs/.vuepress/client.ts:
--------------------------------------------------------------------------------
1 | import { defineClientConfig } from '@vuepress/client'
2 | import { library } from '@fortawesome/fontawesome-svg-core'
3 | import { faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'
4 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
5 |
6 | library.add(faDiscord, faGithub)
7 |
8 | export default defineClientConfig({
9 | enhance({ app }) {
10 | app.component('font-awesome-icon', FontAwesomeIcon)
11 | },
12 | })
--------------------------------------------------------------------------------
/docs/guide/debugging/index.md:
--------------------------------------------------------------------------------
1 | ## Test plan debugging
2 |
3 | A usual requirement while building a test plan is to be able to review requests and responses and debug the test plan for potential issues in the configuration or behavior of the service under test. With JMeter DSL you have several options for this purpose.
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/cookies-and-cache.md:
--------------------------------------------------------------------------------
1 | #### Cookies & caching
2 |
3 | JMeter DSL automatically adds a cookie manager and cache manager for automatic HTTP cookie and caching handling, emulating a browser behavior. If you need to disable them you can use something like this:
4 |
5 | ```cs
6 | TestPlan(
7 | HttpCookies().Disable(),
8 | HttpCache().Disable(),
9 | ThreadGroup(2, 10,
10 | HttpSampler("http://my.service")
11 | )
12 | )
13 | ```
14 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Azure.Tests/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.BlazeMeter.Tests/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Configs/BaseConfigElement.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Configs
4 | {
5 | ///
6 | /// Contains common logic for config elements defined by the DSL.
7 | ///
8 | public abstract class BaseConfigElement : BaseTestElement, IMultiLevelTestElement
9 | {
10 | public BaseConfigElement(string name)
11 | : base(name)
12 | {
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/fix-docs-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script takes care of setting proper JMeter DSL version in docs.
4 | #
5 | set -eo pipefail
6 |
7 | VERSION=$1
8 |
9 | update_file_versions() {
10 | local VERSION="$1"
11 | local FILE="$2"
12 | sed -i "s/--version [0-9.]\+/--version ${VERSION}/g" "${FILE}"
13 | }
14 |
15 | update_file_versions ${VERSION} README.md
16 |
17 | find docs -name "*.md" -not -path "*/node_modules/*" | while read DOC_FILE; do
18 | update_file_versions ${VERSION} ${DOC_FILE}
19 | done
--------------------------------------------------------------------------------
/docs/guide/reporting/index.md:
--------------------------------------------------------------------------------
1 | ## Reporting
2 |
3 | Once you have a test plan you would usually want to be able to analyze the collected information. This section contains a few ways to achieve this, but in the future, we plan to support more ([as Java DSL does](https://abstracta.github.io/jmeter-java-dsl/guide/#reporting)). If you are interested in some not yet covered feature, please ask for it by creating an [issue in the repository](https://github.com/abstracta/jmeter-dotnet-dsl/issues).
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/IDslTestElement.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core
2 | {
3 | ///
4 | /// Interface to be implemented by all elements composing a JMeter test plan.
5 | ///
6 | public interface IDslTestElement
7 | {
8 | ///
9 | /// Shows the test element in it's defined GUI in a popup window.
10 | ///
11 | /// This might be handy to visualize the element as it looks in JMeter GUI.
12 | ///
13 | void ShowInGui();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/TestElements/IMultiLevelTestElement.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Samplers;
2 | using Abstracta.JmeterDsl.Core.ThreadGroups;
3 |
4 | namespace Abstracta.JmeterDsl.Core.TestElements
5 | {
6 | ///
7 | /// This is just a simple interface to avoid code duplication for test elements that apply at
8 | /// different levels of a test plan(at test plan, thread group or as sampler child).
9 | ///
10 | public interface IMultiLevelTestElement : ITestPlanChild, IThreadGroupChild, ISamplerChild
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/ThreadGroups/BaseThreadGroup.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 |
3 | namespace Abstracta.JmeterDsl.Core.ThreadGroups
4 | {
5 | ///
6 | /// Contains common logic for all Thread Groups.
7 | ///
8 | public abstract class BaseThreadGroup : TestElementContainer, ITestPlanChild
9 | where T : BaseThreadGroup
10 | {
11 | protected BaseThreadGroup(string name, IThreadGroupChild[] children)
12 | : base(name, children)
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Stats/CountMetricSummary.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Stats
2 | {
3 | ///
4 | /// Provides summary data for a set of count values.
5 | ///
6 | public class CountMetricSummary
7 | {
8 | ///
9 | /// Provides the total count (the sum).
10 | ///
11 | public long Total { get; set; }
12 |
13 | ///
14 | /// Provides the average count per second for the given metric.
15 | ///
16 | public double PerSecond { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/docs/.vuepress/plugins/includedRelativeLinkSolverPlugin.ts:
--------------------------------------------------------------------------------
1 | import mditPlugin from 'markdown-it-replace-link'
2 |
3 | export const includedRelativeLinkSolverPlugin = ({}) => ({
4 | name: 'includedRelativeLinkSolverPlugin',
5 | extendsMarkdown: (md) => {
6 | md.use(mditPlugin, {
7 | replaceLink: (link, env) => {
8 | if (link.startsWith('.')) {
9 | let hashPos = link.indexOf('#')
10 | return hashPos > 0 ? link.substring(hashPos) : link
11 | }
12 | return link
13 | }
14 | })
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/TestElements/BaseTestElement.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Bridge;
2 |
3 | namespace Abstracta.JmeterDsl.Core.TestElements
4 | {
5 | ///
6 | /// Provides the basic logic for all .
7 | ///
8 | public abstract class BaseTestElement : IDslTestElement
9 | {
10 | protected readonly string _name;
11 |
12 | public BaseTestElement(string name)
13 | {
14 | _name = name;
15 | }
16 |
17 | public void ShowInGui() =>
18 | new BridgeService().ShowTestElementInGui(this);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/docs/guide/setup.md:
--------------------------------------------------------------------------------
1 | ## Setup
2 |
3 | To use the DSL just include it in your project:
4 |
5 | ```powershell
6 | dotnet add package Abstracta.JmeterDsl --version 0.8
7 | ```
8 |
9 | ::: tip
10 | [Here](https://github.com/abstracta/jmeter-dotnet-dsl-sample) is a sample project in case you want to start one from scratch.
11 | :::
12 |
13 | ::: warning
14 | JMeter .Net DSL uses existing JMeter Java DSL which in turn uses JMeter. JMeter Java DSL and JMeter are Java based tools. So, **Java 8+ is required** for the proper execution of DSL test plans. One option is downloading a JVM from [Adoptium](https://adoptium.net/) if you don't have one already.
15 | :::
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | csharp_style_expression_bodied_methods=when_on_single_line
4 | dotnet_analyzer_diagnostic.severity = error
5 | dotnet_diagnostic.CS1591.severity = none
6 | dotnet_diagnostic.CA1822.severity = none
7 | dotnet_diagnostic.IDE0008.severity = none
8 | dotnet_diagnostic.IDE0039.severity = none
9 | dotnet_diagnostic.IDE0052.severity = none
10 | dotnet_diagnostic.IDE0058.severity = none
11 | dotnet_diagnostic.IDE0063.severity = none
12 | dotnet_diagnostic.IDE0065.severity = none
13 | dotnet_diagnostic.IDE0090.severity = none
14 | dotnet_diagnostic.IDE0270.severity = none
15 | dotnet_diagnostic.JSON002.severity = none
16 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Bridge/IDslProperty.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Bridge
2 | {
3 | ///
4 | /// This is just a marker interface to properly serialize properties that can include multiple values.
5 | ///
6 | /// Such properties are added to a __propList c# class property and a class implementing IDslProperty is used to define the name of the property.
7 | ///
8 | /// and for examples of classes using __propList and IDslProperty interface.
9 | ///
10 | public interface IDslProperty
11 | {
12 | }
13 | }
--------------------------------------------------------------------------------
/.github/vuepress-deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script takes care of building and deploying the vuepress documentation to github pages.
4 | #
5 | set -eo pipefail
6 |
7 | cd docs
8 |
9 | pnpm install && pnpm build
10 |
11 | cd .vuepress/dist
12 |
13 | EMAIL="$(git log --format='%ae' HEAD^!)"
14 | USERNAME="$(git log --format='%an' HEAD^!)"
15 | git init
16 | git config --local user.email "$EMAIL"
17 | git config --local user.name "$USERNAME"
18 | git add .
19 | git commit -m '[skip ci] Deploy docs to GitHub pages'
20 |
21 | git push -f https://git:${ACCESS_TOKEN}@github.com/abstracta/jmeter-dotnet-dsl.git master:gh-pages
22 |
23 | cd $GITHUB_WORKSPACE
24 |
--------------------------------------------------------------------------------
/devbox.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "maven@latest",
4 | "nodejs@latest",
5 | "nodePackages.pnpm@latest",
6 | "temurin-bin-8@latest",
7 | "dotnet-sdk@latest"
8 | ],
9 | "shell": {
10 | "init_hook": [
11 | "echo 'Welcome to devbox!' > /dev/null"
12 | ],
13 | "scripts": {
14 | "setup": [
15 | "pnpm --dir docs install"
16 | ],
17 | "docs": [
18 | "pnpm --dir docs dev"
19 | ],
20 | "clean": [
21 | "dotnet clean"
22 | ],
23 | "build": [
24 | "dotnet build"
25 | ],
26 | "test": [
27 | "dotnet test"
28 | ]
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/index.md:
--------------------------------------------------------------------------------
1 | ### HTTP
2 |
3 | Throughout this guide, several examples have been shown for simple cases of HTTP requests (mainly how to do gets and posts), but the DSL provides additional features that you might need to be aware of.
4 |
5 | Here we show some of them, but check [JmeterDsl](/Abstracta.JmeterDsl/JmeterDsl.cs) and [DslHttpSampler](/Abstracta.JmeterDsl/Http/DslHttpSampler.cs) to explore all available features.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Bridge/JvmException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Bridge
4 | {
5 | ///
6 | /// Exception thrown when there is some problem interacting with Java Virutal Machine or executing a particular command.
7 | ///
8 | /// This might be to some exception generated in test plan execution, showing element in GUI, or event issues with JVM initialization.
9 | ///
10 | public class JvmException : Exception
11 | {
12 | public JvmException()
13 | : base("JVM execution failed. Check stderr and stdout for additional info.")
14 | {
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/methods-and-body.md:
--------------------------------------------------------------------------------
1 | #### Methods & body
2 |
3 | As previously seen, you can do simple gets and posts like in the following snippet:
4 |
5 | ```cs
6 | HttpSampler("http://my.service") // A simple get
7 | HttpSampler("http://my.service")
8 | .Post("{\"field\":\"val\"}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json)) // simple post
9 | ```
10 |
11 | But you can also use additional methods to specify any HTTP method and body:
12 |
13 | ```cs
14 | HttpSampler("http://my.service")
15 | .Method(HttpMethod.Put.Method)
16 | .ContentType(new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
17 | .Body("{\"field\":\"val\"}")
18 | ```
19 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Controllers/DslTransactionControllerTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Controllers
2 | {
3 | using static JmeterDsl;
4 |
5 | public class DslTransactionControllerTest
6 | {
7 | [Test]
8 | public void ShouldIncludeTransactionSampleInResultsWhenTestPlanWithTransaction()
9 | {
10 | TestPlanStats stats = TestPlan(
11 | ThreadGroup(1, 1,
12 | Transaction("My Transaction",
13 | DummySampler("ok")
14 | )
15 | )
16 | ).Run();
17 | Assert.That(stats.Overall.SamplesCount, Is.EqualTo(2));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Controllers/ForLoopControllerTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Controllers
2 | {
3 | using static JmeterDsl;
4 |
5 | public class ForLoopControllerTest
6 | {
7 | [Test]
8 | public void ShouldGetExpectedSamplerCountWhenSamplerInsideLoopController()
9 | {
10 | var loopIterations = 3;
11 | var stats = TestPlan(
12 | ThreadGroup(1, 1,
13 | ForLoopController(loopIterations,
14 | DummySampler("ok")
15 | )
16 | )).Run();
17 | Assert.That(stats.Overall.SamplesCount, Is.EqualTo(loopIterations));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Timers/DslConstantTimerTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Timers
2 | {
3 | using System;
4 | using static JmeterDsl;
5 |
6 | public class DslConstantTimerTest
7 | {
8 | [Test]
9 | public void ShouldLastAtLeastConfiguredTimeWhenUsingConstantTimer()
10 | {
11 | var timerDuration = TimeSpan.FromSeconds(5);
12 | var stats = TestPlan(
13 | ThreadGroup(1, 1,
14 | ConstantTimer(timerDuration),
15 | DummySampler("OK")
16 | )
17 | ).Run();
18 | Assert.That(stats.Duration, Is.GreaterThan(timerDuration));
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/IDslJmeterEngine.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core
2 | {
3 | ///
4 | /// Interface to be implemented by classes allowing to run a DslTestPlan in different engines.
5 | ///
6 | public interface IDslJmeterEngine
7 | {
8 | ///
9 | /// Runs the given test plan obtaining the execution metrics.
10 | ///
11 | /// This method blocks execution until the test plan execution ends.
12 | ///
13 | /// to run in the JMeter engine.
14 | /// the metrics associated to the run.
15 | TestPlanStats Run(DslTestPlan testPlan);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Samplers/DslFlowControlActionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Samplers
4 | {
5 | using static JmeterDsl;
6 |
7 | public class DslFlowControlActionTest
8 | {
9 | [Test]
10 | public void ShouldLastAtLeastConfiguredTimeWhenUsingConstantTimer()
11 | {
12 | var timerDuration = TimeSpan.FromSeconds(5);
13 | var stats = TestPlan(
14 | ThreadGroup(1, 1,
15 | ThreadPause(timerDuration),
16 | DummySampler("OK")
17 | )
18 | ).Run();
19 | Assert.That(stats.Duration, Is.GreaterThan(timerDuration));
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/docs/guide/scale/index.md:
--------------------------------------------------------------------------------
1 | ## Run test at scale
2 |
3 | Running a load test from one machine is not always enough, since you are limited to the machine's hardware capabilities. Sometimes, is necessary to run the test using a cluster of machines to be able to generate enough load for the system under test. Currently, the .Net DSL only provides two ways to run tests at scale, but in the future, we plan to support more ([as Java DSL does](https://abstracta.github.io/jmeter-java-dsl/guide/#run-test-at-scale)). If you are interested in some not yet covered feature, please ask for it by creating an [issue in the repository](https://github.com/abstracta/jmeter-dotnet-dsl/issues).
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Timers/DslUniformRandomTimerTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Timers
2 | {
3 | using System;
4 | using static JmeterDsl;
5 |
6 | public class DslUniformRandomTimerTest
7 | {
8 | [Test]
9 | public void ShouldLastAtLeastMinimumTimeWhenUsingRandomUniformTimer()
10 | {
11 | var minimum = TimeSpan.FromSeconds(5);
12 | var stats = TestPlan(
13 | ThreadGroup(1, 1,
14 | UniformRandomTimer(minimum, TimeSpan.FromSeconds(7)),
15 | DummySampler("OK")
16 | )
17 | ).Run();
18 | Assert.That(stats.Duration, Is.GreaterThan(minimum));
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/update-sample-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script takes care of updating JMeter DSL version in sample project.
4 | #
5 | set -eo pipefail
6 |
7 | VERSION=$1
8 |
9 | USER_EMAIL="$(git log --format='%ae' HEAD^!)"
10 | USER_NAME="$(git log --format='%an' HEAD^!)"
11 |
12 | cd jmeter-dotnet-dsl-sample
13 | sed -i "s/Include=\"Abstracta.JmeterDsl\" Version=\"[0-9.]\+\"/Include=\"Abstracta.JmeterDsl\" Version=\"${VERSION}\"/g" Abstracta.JmeterDsl.Sample/Abstracta.JmeterDsl.Sample.csproj
14 |
15 | git add .
16 | git config --local user.email "$USER_EMAIL"
17 | git config --local user.name "$USER_NAME"
18 | git commit -m "Updated Abstracta.JmeterDsl version"
19 | git push origin HEAD:master
20 | cd ..
21 | rm -rf jmeter-dotnet-dsl-sample
22 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/PreProcessors/DslJsr223PreProcessorTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.PreProcessors
2 | {
3 | using static JmeterDsl;
4 |
5 | public class DslJsr223PreProcessorTest
6 | {
7 | [Test]
8 | public void ShouldUseDefinedLabelWhenPreProcessorSettingLabelInTestPlan()
9 | {
10 | var stats = TestPlan(
11 | ThreadGroup(1, 1,
12 | DummySampler("ok")
13 | .Children(
14 | Jsr223PreProcessor("sampler.name = 'test'")
15 | )
16 | )).Run();
17 | Assert.That(stats.Labels["test"].SamplesCount, Is.EqualTo(1));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/TestElements/DslJsr223TestElement.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.TestElements
2 | {
3 | ///
4 | /// Abstracts common logic used by JSR223 test elements.
5 | ///
6 | public abstract class DslJsr223TestElement : BaseTestElement
7 | where T : DslJsr223TestElement
8 | {
9 | protected readonly string _script;
10 | protected string _language;
11 |
12 | public DslJsr223TestElement(string name, string script)
13 | : base(name)
14 | {
15 | _script = script;
16 | }
17 |
18 | public T Language(string language)
19 | {
20 | _language = language;
21 | return (T)this;
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/PostProcessors/DslVariableExtractor.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 |
3 | namespace Abstracta.JmeterDsl.Core.PostProcessors
4 | {
5 | ///
6 | /// Contains common logic for post processors which extract some value into a variable.
7 | ///
8 | public abstract class DslVariableExtractor : DslScopedTestElement
9 | where T : DslVariableExtractor
10 | {
11 | protected readonly string _variableName;
12 | protected int? _matchNumber;
13 | protected string _defaultValue;
14 |
15 | public DslVariableExtractor(string name, string varName)
16 | : base(name)
17 | {
18 | _variableName = varName;
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/.github/semver-check.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script checks that the version number of the release is an expected one, and avoid erroneous releases which don't follow semver
4 | set -eo pipefail
5 |
6 | git fetch --tags --quiet
7 | VERSION="$1"
8 | PREV_VERSION=$(git tag --sort=-creatordate | head -1)
9 | PREV_VERSION=${PREV_VERSION:-v0.0}
10 | PREV_VERSION=${PREV_VERSION#v}
11 | PREV_MAJOR="${PREV_VERSION%%.*}"
12 | PREV_VERSION="${PREV_VERSION#*.}"
13 | PREV_MINOR="${PREV_VERSION%%.*}"
14 | PREV_PATCH="${PREV_VERSION#*.}"
15 | if [[ "$PREV_VERSION" == "$PREV_PATCH" ]]; then
16 | PREV_PATCH="0"
17 | fi
18 |
19 | [[ "$VERSION" == "$PREV_MAJOR.$PREV_MINOR.$((PREV_PATCH + 1))" || "$VERSION" == "$PREV_MAJOR.$((PREV_MINOR + 1))" || "$VERSION" == "$((PREV_MAJOR + 1)).0" ]]
20 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Samplers/DslFlowControlAction.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Bridge;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Samplers
4 | {
5 | ///
6 | /// Uses JMeter Flow Control Action to allow taking different actions (stop, pause, interrupt).
7 | ///
8 | [YamlType(TagName = "threadPause")]
9 | public class DslFlowControlAction : BaseSampler
10 | {
11 | private readonly string _duration;
12 |
13 | private DslFlowControlAction(string duration)
14 | : base(null)
15 | {
16 | _duration = duration;
17 | }
18 |
19 | public static DslFlowControlAction PauseThread(string duration) =>
20 | new DslFlowControlAction(duration);
21 | }
22 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 | us.abstracta.jmeter.dotnet
7 | jmeter-dotnet-dsl-parent
8 | 1.0-SNAPSHOT
9 | pom
10 |
11 | This pom is only needed to be able to copy jmeter-java-dsl jars and dependencies with dependency:copy-dependencies maven plugin goal in child projects
12 |
13 |
14 | 1.29.1
15 |
16 |
--------------------------------------------------------------------------------
/docs/guide/thread-groups/index.md:
--------------------------------------------------------------------------------
1 | ## Advanced threads configuration
2 |
3 | JMeter DSL provides two simple ways of creating thread groups which are used in most scenarios:
4 |
5 | * specifying threads and the number of iterations each thread should execute before ending the test plan
6 | * specifying threads and duration for which each thread should execute before the test plan ends
7 |
8 | This is how they look in code:
9 |
10 | ```cs
11 | ThreadGroup(10, 20, ...) // 10 threads for 20 iterations each
12 | ThreadGroup(10, TimeSpan.FromSeconds(20), ...) // 10 threads for 20 seconds each
13 | ```
14 |
15 | But these options are not good when working with many threads or when trying to configure some complex test scenarios (like when doing incremental or peak tests).
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Configs/DslCsvDataSetTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Configs
2 | {
3 | using static JmeterDsl;
4 |
5 | public class DslCsvDataSetTest
6 | {
7 | [Test]
8 | public void ShouldGetExpectedSamplesWhenTestPlanWithSampleNamesFromCsvDataSet()
9 | {
10 | var stats = TestPlan(
11 | CsvDataSet("Core/Configs/data.csv"),
12 | ThreadGroup(1, 2,
13 | DummySampler("${VAR1}-${VAR2}", "ok")
14 | )).Run();
15 | Assert.Multiple(() =>
16 | {
17 | Assert.That(stats.Labels["val1-val2"].SamplesCount, Is.EqualTo(1));
18 | Assert.That(stats.Labels["val,3-val4"].SamplesCount, Is.EqualTo(1));
19 | });
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Bridge/EnumConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 | using YamlDotNet.Core;
4 | using YamlDotNet.Core.Events;
5 | using YamlDotNet.Serialization;
6 |
7 | namespace Abstracta.JmeterDsl.Core.Bridge
8 | {
9 | public class EnumConverter : IYamlTypeConverter
10 | {
11 | public bool Accepts(Type type)
12 | => type.IsEnum;
13 |
14 | public object ReadYaml(IParser parser, Type type)
15 | => throw new NotImplementedException();
16 |
17 | public void WriteYaml(IEmitter emitter, object value, Type type)
18 | => emitter.Emit(new Scalar(ToSnakeCase(value.ToString())));
19 |
20 | private string ToSnakeCase(string val)
21 | => Regex.Replace(val, @"([a-z0-9])([A-Z])", "$1_$2").ToUpper();
22 | }
23 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Controllers/DslSimpleControllerTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Controllers
2 | {
3 | using static JmeterDsl;
4 |
5 | public class DslSimpleControllerTest
6 | {
7 | [Test]
8 | public void ShouldApplyAssertionToSimpleControllerScopedElements()
9 | {
10 | var body = "FAIL";
11 | var stats = TestPlan(
12 | ThreadGroup(1, 1,
13 | SimpleController(
14 | ResponseAssertion()
15 | .ContainsSubstrings("OK"),
16 | DummySampler(body),
17 | DummySampler(body)
18 | ),
19 | DummySampler(body)
20 | )
21 | ).Run();
22 | Assert.That(stats.Overall.ErrorsCount, Is.EqualTo(2));
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/HomeFeatures.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
{{ feature.title }}
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Controllers/ForLoopController.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.ThreadGroups;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Controllers
4 | {
5 | ///
6 | /// Allows running part of a test plan a given number of times inside one thread group iteration.
7 | ///
8 | /// Internally this uses JMeter Loop Controller.
9 | ///
10 | /// JMeter automatically creates a variable named __jm__<controllerName>__idx which contains
11 | /// the index of the iteration starting with zero.
12 | ///
13 | public class ForLoopController : BaseController
14 | {
15 | private readonly string _count;
16 |
17 | public ForLoopController(string name, string count, IThreadGroupChild[] children)
18 | : base(name, children)
19 | {
20 | _count = count;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/.github/fix-project-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script takes care of setting proper JMeter DSL version in projects.
4 | #
5 | set -eo pipefail
6 |
7 | VERSION=$1
8 |
9 | update_file_versions() {
10 | local VERSION="$1"
11 | local FILE="$2"
12 | local BASE_VERSION="${VERSION%%-*}"
13 | local ASSEMBLY_VERSION="${BASE_VERSION}.0"
14 | if [ $(echo "${BASE_VERSION}" | awk -F"." '{print NF-1}') == 1 ]; then
15 | ASSEMBLY_VERSION="${ASSEMBLY_VERSION}.0"
16 | fi
17 | sed -i "s/[^<]\+<\/AssemblyVersion>/${ASSEMBLY_VERSION}<\/AssemblyVersion>/g" "${FILE}"
18 | sed -i "s/[^<]\+<\/Version>/${VERSION}<\/Version>/g" "${FILE}"
19 | sed -i "s/[^<]\+<\/FileVersion>/${BASE_VERSION}<\/FileVersion>/g" "${FILE}"
20 | }
21 |
22 | find . -name "*.csproj" | while read DOC_FILE; do
23 | update_file_versions ${VERSION} ${DOC_FILE}
24 | done
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Controllers/DslSimpleController.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.ThreadGroups;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Controllers
4 | {
5 | ///
6 | /// Builds a Simple Controller that allows defining new JMeter scope for other elements to apply.
7 | ///
8 | /// This is handy for example to apply timers, configs, listeners, assertions, pre- and
9 | /// post-processors to only some samplers in the test plan.
10 | ///
11 | /// It has a similar functionality as the transaction controller, but it doesn't add any additional
12 | /// sample results (statistics) to the test plan.
13 | ///
14 | public class DslSimpleController : BaseController
15 | {
16 | public DslSimpleController(string name, IThreadGroupChild[] children)
17 | : base(name, children)
18 | {
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Engines/BaseJmeterEngine.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Bridge;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace Abstracta.JmeterDsl.Core.Engines
5 | {
6 | public abstract class BaseJmeterEngine : IDslJmeterEngine
7 | where T : BaseJmeterEngine
8 | {
9 | [YamlIgnore]
10 | protected string _jvmArgs = string.Empty;
11 |
12 | ///
13 | /// Specifies arguments to be added to JVM command line.
14 | ///
15 | /// This is helpful, for example, when debugging JMeter DSL or JMeter code.
16 | ///
17 | public T JvmArgs(string args)
18 | {
19 | _jvmArgs = args;
20 | return (T)this;
21 | }
22 |
23 | public TestPlanStats Run(DslTestPlan testPlan) =>
24 | new BridgeService()
25 | .JvmArgs(_jvmArgs)
26 | .RunTestPlanInEngine(testPlan, this);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/guide/debugging/show-in-gui.md:
--------------------------------------------------------------------------------
1 | ### Test plan review un JMeter GUI
2 |
3 | A usual requirement for new DSL users that are used to Jmeter GUI, is to be able to review Jmeter DSL generated test plan in the familiar JMeter GUI. For this, you can use the `ShowInGui()` method in a test plan to open JMeter GUI with the preloaded test plan.
4 |
5 | This can be also used to debug the test plan, by adding elements (like view results tree, dummy samplers, etc.) in the GUI and running the test plan.
6 |
7 | Here is a simple example using the method:
8 |
9 | ```cs
10 | using static Abstracta.JmeterDsl.JmeterDsl;
11 |
12 | public class PerformanceTest
13 | {
14 | [Test]
15 | public void LoadTest()
16 | {
17 | var stats = TestPlan(
18 | ThreadGroup(2, 10,
19 | HttpSampler("http://my.service")
20 | )
21 | ).ShowInGui();
22 | }
23 | }
24 | ```
25 |
26 | Which ends up opening a window like this one:
27 |
28 | 
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Timers/DslConstantTimer.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Timers
2 | {
3 | ///
4 | /// Allows using JMeter Constant Timers which pause the thread for a given period.
5 | ///
6 | /// The pause calculated by the timer will be applied after samplers pre-processors execution and
7 | /// before actual sampling.
8 | ///
9 | /// Take into consideration that timers applies to all samplers in their scope: if added at test plan
10 | /// level, it will apply to all samplers in test plan; if added at thread group level, it will apply
11 | /// only to samples in such thread group; if added as child of a sampler, it will only apply to that
12 | /// sampler.
13 | ///
14 | public class DslConstantTimer : BaseTimer
15 | {
16 | private readonly string _duration;
17 |
18 | public DslConstantTimer(string duration)
19 | : base(null)
20 | {
21 | _duration = duration;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/TestElements/TestElementContainer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Abstracta.JmeterDsl.Core.TestElements
5 | {
6 | ///
7 | /// Abstracts logic for that can nest other test elements.
8 | ///
9 | /// is type of sub classes.
10 | /// is type of test elements that can be nested by this class.
11 | public abstract class TestElementContainer : BaseTestElement
12 | where T : TestElementContainer
13 | {
14 | protected List _children = new List();
15 |
16 | protected TestElementContainer(string name, C[] children)
17 | : base(name)
18 | {
19 | _children = children.ToList();
20 | }
21 |
22 | protected T Children(params C[] chilren)
23 | {
24 | _children.AddRange(chilren);
25 | return (T)this;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/multipart.md:
--------------------------------------------------------------------------------
1 | #### Multipart requests
2 |
3 | When you need to upload files to an HTTP server or need to send a complex request body, you will in many cases require sending multipart requests. To send a multipart request just use `BodyPart` and `BodyFilePart` methods like in the following example:
4 |
5 | ```cs
6 | using static Abstracta.JmeterDsl.JmeterDsl;
7 | using System.Net.Http;
8 | using System.Net.Http.Headers;
9 | using System.Net.Mime;
10 |
11 | public class PerformanceTest
12 | {
13 | [Test]
14 | public void LoadTest()
15 | {
16 | TestPlan(
17 | ThreadGroup(1, 1,
18 | HttpSampler("https://myservice.com/report"),
19 | .Method(HttpMethod.Post.Method)
20 | .BodyPart("myText", "Hello World", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain))
21 | .BodyFilePart("myFile", "myReport.xml", new MediaTypeHeaderValue(MediaTypeNames.Text.Xml))
22 | )
23 | ).Run();
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: run tests
2 | on:
3 | push:
4 | tags-ignore:
5 | - "*"
6 | branches:
7 | - "**"
8 | jobs:
9 | test:
10 | runs-on: ubuntu-latest
11 | concurrency: azure_test
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Setup .NET Core
15 | uses: actions/setup-dotnet@v4
16 | with:
17 | dotnet-version: 6
18 | - uses: actions/setup-java@v4
19 | with:
20 | distribution: temurin
21 | java-version: 11
22 | cache: maven
23 | - name: Install dependencies
24 | run: dotnet restore
25 | - name: Build
26 | run: dotnet build --configuration Release --no-restore
27 | - name: Test
28 | # avoid running tests in parallel since it may happen that two tests try to modify .jmeter-dsl directory
29 | run: dotnet test -m:1 --no-build --configuration Release --verbosity normal
30 | env:
31 | AZURE_CREDS: ${{ secrets.AZURE_CREDS }}
32 | BZ_TOKEN: ${{ secrets.BZ_TOKEN }}
33 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Listeners/ResponseFileSaverTest.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Listeners
4 | {
5 | using static JmeterDsl;
6 |
7 | public class ResponseFileSaverTest
8 | {
9 | [Test]
10 | public void ShouldWriteFileWithResponseContentWhenResponseFileSaverInPlan()
11 | {
12 | var workDir = new DirectoryInfo("responses");
13 | try
14 | {
15 | var body = "ok";
16 | TestPlan(
17 | ThreadGroup(1, 1,
18 | DummySampler(body),
19 | ResponseFileSaver(Path.Combine(workDir.FullName, "response"))
20 | )).Run();
21 | var responseFileBody = File.ReadAllText(Path.Combine(workDir.FullName, "response1.unknown"));
22 | Assert.That(responseFileBody, Is.EqualTo(body));
23 | }
24 | finally
25 | {
26 | workDir.Delete(true);
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/ThreadGroups/DslThreadGroupTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.ThreadGroups
4 | {
5 | using static JmeterDsl;
6 |
7 | public class DslThreadGroupTest
8 | {
9 | [Test]
10 | public void ShouldMakeOneRequestWhenOneThreadAndIteration()
11 | {
12 | var stats = TestPlan(
13 | ThreadGroup(threads: 1, iterations: 1,
14 | DummySampler("OK")
15 | )
16 | ).Run();
17 | Assert.That(stats.Overall.SamplesCount, Is.EqualTo(1));
18 | }
19 |
20 | [Test]
21 | public void ShouldTakeAtLeastDurationWhenThreadGroupWithDuration()
22 | {
23 | var duration = TimeSpan.FromSeconds(5);
24 | var stats = TestPlan(
25 | ThreadGroup(threads: 1, duration: duration,
26 | DummySampler("OK")
27 | )
28 | ).Run();
29 | Assert.That(stats.Duration, Is.GreaterThanOrEqualTo(duration));
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/TestPlanStats.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Abstracta.JmeterDsl.Core.Stats;
4 |
5 | namespace Abstracta.JmeterDsl.Core
6 | {
7 | ///
8 | /// Contains all statistics collected during the execution of a test plan.
9 | ///
10 | /// When using different samples, specify different names on them to be able to get each sampler
11 | /// specific statistics after they run.
12 | ///
13 | public class TestPlanStats
14 | {
15 | ///
16 | /// Provides the time taken to run the test plan.
17 | ///
18 | public TimeSpan Duration { get; set; }
19 |
20 | ///
21 | /// Provides statistics for the entire test plan.
22 | ///
23 | public StatsSummary Overall { get; set; }
24 |
25 | ///
26 | /// Provides statistics for each label (usually, samplers labels).
27 | ///
28 | public Dictionary Labels { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Azure.Tests/Abstracta.JmeterDsl.Azure.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | false
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/PreProcessors/DslJsr223PreProcessor.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 |
3 | namespace Abstracta.JmeterDsl.Core.PreProcessors
4 | {
5 | ///
6 | /// Allows running custom logic before executing a sampler.
7 | ///
8 | /// This is a very powerful and flexible component that allows you to modify variables, sampler,
9 | /// context, etc., before running a sampler (for example to generate dynamic requests
10 | /// programmatically).
11 | ///
12 | /// By default, provided script will be interpreted as groovy script, which is the default setting
13 | /// for JMeter. If you need, you can use any of JMeter provided scripting languages (beanshell,
14 | /// javascript, jexl, etc.) by setting the property.
15 | ///
16 | public class DslJsr223PreProcessor : DslJsr223TestElement, IMultiLevelTestElement
17 | {
18 | public DslJsr223PreProcessor(string name, string script)
19 | : base(name, script)
20 | {
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | us.abstracta.jmeter.dotnet
8 | jmeter-dotnet-dsl-parent
9 | 1.0-SNAPSHOT
10 | ../pom.xml
11 |
12 | jmeter-dotnet-dsl
13 | pom
14 |
15 | This pom is only needed to be able to copy jmeter-java-dsl-bridge jar and its dependencies with dependency:copy-dependencies maven plugin goal
16 |
17 |
18 |
19 | us.abstracta.jmeter
20 | jmeter-java-dsl-bridge
21 | ${jmeter-java-dsl.version}
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.BlazeMeter.Tests/Abstracta.JmeterDsl.BlazeMeter.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | false
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Listeners/JtlWriterTest.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Listeners
4 | {
5 | using static JmeterDsl;
6 |
7 | public class JtlWriterTest
8 | {
9 | [Test]
10 | public void ShouldWriteResultsToFileWhenJtlWriterAtTestPlan()
11 | {
12 | DirectoryInfo workDir = new DirectoryInfo("jtls");
13 | try
14 | {
15 | TestPlan(
16 | ThreadGroup(1, 1,
17 | DummySampler("ok"),
18 | JtlWriter(workDir.FullName)
19 | )).Run();
20 | Assert.That(GetFileLinesCount(FindJtlFileInDirectory(workDir)), Is.EqualTo(2));
21 | }
22 | finally
23 | {
24 | workDir.Delete(true);
25 | }
26 | }
27 |
28 | private FileInfo FindJtlFileInDirectory(DirectoryInfo workDir) =>
29 | workDir.GetFiles("*.jtl")[0];
30 |
31 | private int GetFileLinesCount(FileInfo jtlFile) =>
32 | File.ReadAllLines(jtlFile.FullName).Length;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/guide/request-generation/timers/synchronizing-timer.md:
--------------------------------------------------------------------------------
1 | #### Requests synchronization
2 |
3 | Usually, samples generated by different threads in a test plan thread group start deviating from each other according to the different durations each of them may experience.
4 |
5 | Here is a diagram depicting this behavior, extracted from [this nice example](https://github.com/abstracta/jmeter-java-dsl/discussions/204) provided by one of JMeter DSL users:
6 |
7 | 
8 |
9 | In most cases this is ok. But, if you want to generate batches of simultaneous requests to a system under test, this variability will prevent you from getting the expected behavior.
10 |
11 | So, to synchronize requests, by holding some of them until all are in sync, like in this diagram:
12 |
13 | 
14 |
15 | You can use `SynchronizingTimer` like in the following example:
16 |
17 | ```cs
18 | TestPlan(
19 | ThreadGroup(2, 3,
20 | HttpSample("https://mysite"),
21 | SynchronizingTimer()
22 | )
23 | )
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/guide/response-processing/correlation/regex-extractor.md:
--------------------------------------------------------------------------------
1 | #### Regular expressions extraction
2 |
3 | Here is an example with JMeter DSL using regular expressions:
4 |
5 | ```cs
6 | using System.Net.Http.Headers;
7 | using System.Net.Mime;
8 |
9 | using static Abstracta.JmeterDsl.JmeterDsl;
10 |
11 | public class PerformanceTest
12 | {
13 | [Test]
14 | public void LoadTest()
15 | {
16 | var stats = TestPlan(
17 | ThreadGroup(2, 10,
18 | HttpSampler("http://my.service/accounts")
19 | .Post("{\"name\": \"John Doe\"}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
20 | .Children(
21 | RegexExtractor("ACCOUNT_ID", "\"id\":\"([^\"]+)\"")
22 | ),
23 | HttpSampler("http://my.service/accounts/${ACCOUNT_ID}")
24 | )
25 | ).Run();
26 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
27 | }
28 | }
29 | ```
30 |
31 | Check [DslRegexExtractor](/Abstracta.JmeterDsl/Core/PostProcessors/DslRegexExtractor.cs) for more details and additional options.
32 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Listeners/ResponseFileSaver.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Listeners
2 | {
3 | ///
4 | /// Generates one file for each response of a sample/request.
5 | ///
6 | /// This element is dependant of the scope: this means that if you add it at test plan level it will
7 | /// generate files for all samplers in test plan, if added at thread group level then it will
8 | /// generate files for samplers only in the thread group, and if you add it at sampler level it will
9 | /// generate files only for the associated sampler.
10 | ///
11 | /// By default, it will generate one file for each response using the given (which might include the
12 | /// directory location) prefix to create the files and adding an incremental number to each response
13 | /// and an extension according to the response mime type.
14 | ///
15 | public class ResponseFileSaver : BaseListener
16 | {
17 | private readonly string _fileNamePrefix;
18 |
19 | public ResponseFileSaver(string fileNamePrefix)
20 | {
21 | _fileNamePrefix = fileNamePrefix;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Timers/DslUniformRandomTimer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Timers
4 | {
5 | ///
6 | /// Allows specifying JMeter Uniform Random Timers which pause the thread with a random time with
7 | /// uniform distribution.
8 | ///
9 | /// The pause calculated by the timer will be applied after samplers pre-processors execution and
10 | /// before actual sampling.
11 | ///
12 | /// Take into consideration that timers applies to all samplers in their scope: if added at test plan
13 | /// level, it will apply to all samplers in test plan; if added at thread group level, it will apply
14 | /// only to samples in such thread group; if added as child of a sampler, it will only apply to that
15 | /// sampler.
16 | ///
17 | public class DslUniformRandomTimer : BaseTimer
18 | {
19 | private TimeSpan _minimum;
20 | private TimeSpan _maximum;
21 |
22 | public DslUniformRandomTimer(TimeSpan minimum, TimeSpan maximum)
23 | : base(null)
24 | {
25 | _minimum = minimum;
26 | _maximum = maximum;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Http/DslHttpCache.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Configs;
2 |
3 | namespace Abstracta.JmeterDsl.Http
4 | {
5 | ///
6 | /// Allows configuring caching behavior used by HTTP samplers.
7 | ///
8 | /// This element can only be added as child of test plan, and currently allows only to disable HTTP
9 | /// caching which is enabled by default (emulating browser behavior).
10 | ///
11 | /// This element has to be added before any http sampler to be considered, and if you add multiple
12 | /// instances of cache manager to a test plan, only the first one will be considered.
13 | ///
14 | public class DslHttpCache : BaseConfigElement
15 | {
16 | protected bool? _disable;
17 |
18 | public DslHttpCache()
19 | : base(null)
20 | {
21 | }
22 |
23 | ///
24 | /// disables HTTP caching for the test plan.
25 | ///
26 | /// the DslHttpCache to allow fluent API usage.
27 | public DslHttpCache Disable()
28 | {
29 | _disable = true;
30 | return this;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Azure.Tests/AzureEngineTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Abstracta.JmeterDsl.Azure.Tests
5 | {
6 | using static JmeterDsl;
7 |
8 | public class AzureEngineTest
9 | {
10 |
11 | private TextWriter originalConsoleOut;
12 |
13 | // Redirecting output to progress to get live stdout with nunit.
14 | // https://github.com/nunit/nunit3-vs-adapter/issues/343
15 | // https://github.com/nunit/nunit/issues/1139
16 | [SetUp]
17 | public void SetUp()
18 | {
19 | originalConsoleOut = Console.Out;
20 | Console.SetOut(TestContext.Progress);
21 | }
22 |
23 | [TearDown]
24 | public void TearDown() =>
25 | Console.SetOut(originalConsoleOut!);
26 |
27 | [Test]
28 | public void TestInAzure()
29 | {
30 | var stats = TestPlan(
31 | ThreadGroup(1, 1,
32 | HttpSampler("http://localhost")
33 | )
34 | ).RunIn(new AzureEngine(Environment.GetEnvironmentVariable("AZURE_CREDS")));
35 | Assert.That(stats.Overall.ErrorsCount, Is.EqualTo(1));
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jmeter-dotnet-dsl",
3 | "version": "1.0.0",
4 | "description": "Simple JMeter performance tests API",
5 | "main": "index.js",
6 | "repository": "https://github.com/abstracta/jmeter-dotnet-dsl",
7 | "license": "Apache-2.0",
8 | "devDependencies": {
9 | "@fortawesome/fontawesome-svg-core": "^6.4.0",
10 | "@fortawesome/free-brands-svg-icons": "^6.4.0",
11 | "@fortawesome/vue-fontawesome": "^3.0.3",
12 | "@vuepress/cli": "2.0.0-beta.64",
13 | "@vuepress/client": "2.0.0-beta.64",
14 | "@vuepress/plugin-container": "2.0.0-beta.64",
15 | "@vuepress/plugin-medium-zoom": "2.0.0-beta.64",
16 | "@vuepress/plugin-search": "2.0.0-beta.64",
17 | "@vuepress/shared": "2.0.0-beta.64",
18 | "@vuepress/theme-default": "2.0.0-beta.64",
19 | "@vuepress/utils": "2.0.0-beta.64",
20 | "markdown-it-replace-link": "^1.2.0",
21 | "vue": "^3.3.4",
22 | "vue-router": "^4.2.3",
23 | "vuepress": "2.0.0-beta.64",
24 | "vuepress-plugin-copy-code2": "2.0.0-beta.230",
25 | "vuepress-plugin-md-enhance": "2.0.0-beta.230"
26 | },
27 | "scripts": {
28 | "dev": "vuepress dev . --clean-cache --clean-temp",
29 | "build": "vuepress build . --clean-cache --clean-temp"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/headers.md:
--------------------------------------------------------------------------------
1 | #### Headers
2 |
3 | You might have already noticed in some of the examples that we have shown, some ways to set some headers. For instance, in the following snippet, `Content-Type` header is being set in two different ways:
4 |
5 | ```cs
6 | HttpSampler("http://my.service")
7 | .Post("{\"field\":\"val\"}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
8 | HttpSampler("http://my.service")
9 | .ContentType(new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
10 | ```
11 |
12 | These are handy methods to specify the `Content-Type` header, but you can also set any header on a particular request using provided `Header` method, like this:
13 |
14 | ```cs
15 | HttpSampler("http://my.service")
16 | .Header("X-First-Header", "val1")
17 | .Header("X-Second-Header", "val2")
18 | ```
19 |
20 | Additionally, you can specify headers to be used by all samplers in a test plan, thread group, transaction controllers, etc. For this, you can use `HttpHeaders` like this:
21 |
22 | ```cs
23 | TestPlan(
24 | ThreadGroup(2, 10,
25 | HttpHeaders()
26 | .Header("X-Header", "val1"),
27 | HttpSampler("http://my.service"),
28 | HttpSampler("http://my.service/users")
29 | )
30 | ).Run();
31 | ```
32 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/Assertions/DslResponseAssertionTest.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Assertions
2 | {
3 | using static JmeterDsl;
4 |
5 | public class DslResponseAssertionTest
6 | {
7 | [Test]
8 | public void ShouldNotFailAssertionWhenResponseAssertionWithMatchingCondition()
9 | {
10 | var stats = TestPlan(
11 | ThreadGroup(1, 1,
12 | DummySampler("OK")
13 | .Children(
14 | ResponseAssertion().ContainsSubstrings("OK")
15 | )
16 | )).Run();
17 | Assert.That(stats.Overall.ErrorsCount, Is.EqualTo(0));
18 | }
19 |
20 | [Test]
21 | public void ShouldFailAssertionWhenResponseAssertionWithNotMatchingCondition()
22 | {
23 | var stats = TestPlan(
24 | ThreadGroup(1, 1,
25 | DummySampler("OK")
26 | .Children(
27 | ResponseAssertion().ContainsSubstrings("FAIL")
28 | )
29 | )).Run();
30 | Assert.That(stats.Overall.ErrorsCount, Is.EqualTo(1));
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.BlazeMeter.Tests/BlazeMeterEngineTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Abstracta.JmeterDsl.BlazeMeter.Tests
5 | {
6 | using static JmeterDsl;
7 |
8 | public class BlazeMeterEngineTests
9 | {
10 | private TextWriter originalConsoleOut;
11 |
12 | // Redirecting output to progress to get live stdout with nunit.
13 | // https://github.com/nunit/nunit3-vs-adapter/issues/343
14 | // https://github.com/nunit/nunit/issues/1139
15 | [SetUp]
16 | public void SetUp()
17 | {
18 | originalConsoleOut = Console.Out;
19 | Console.SetOut(TestContext.Progress);
20 | }
21 |
22 | [TearDown]
23 | public void TearDown() =>
24 | Console.SetOut(originalConsoleOut!);
25 |
26 | [Test]
27 | public void TestInBlazeMeter()
28 | {
29 | var stats = TestPlan(
30 | ThreadGroup(1, 1,
31 | HttpSampler("http://localhost")
32 | )
33 | ).RunIn(new BlazeMeterEngine(Environment.GetEnvironmentVariable("BZ_TOKEN"))
34 | .UseDebugRun());
35 | Assert.That(stats.Overall.ErrorsCount, Is.EqualTo(1));
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Controllers/BaseController.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.TestElements;
2 | using Abstracta.JmeterDsl.Core.ThreadGroups;
3 |
4 | namespace Abstracta.JmeterDsl.Core.Controllers
5 | {
6 | ///
7 | /// Contains common logic for logic controllers defined by the DSL.
8 | ///
9 | public abstract class BaseController : TestElementContainer, IThreadGroupChild
10 | where T : BaseController
11 | {
12 | protected BaseController(string name, IThreadGroupChild[] children)
13 | : base(name, children)
14 | {
15 | }
16 |
17 | ///
18 | /// Allows specifying children elements that are affected by this controller.
19 | ///
20 | /// This method is helpful to keep general controller settings at the beginning and specify
21 | /// children at last.
22 | ///
23 | /// set of elements to be included in the controller. This list is appended to any children defined in controller builder method.
24 | /// a new controller instance for further configuration or usage.
25 | public new T Children(params IThreadGroupChild[] children)
26 | => base.Children(children);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Azure/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | us.abstracta.jmeter.dotnet
8 | jmeter-dotnet-dsl-parent
9 | 1.0-SNAPSHOT
10 | ../pom.xml
11 |
12 | jmeter-dotnet-dsl-azure
13 | pom
14 |
15 | This pom is only needed to be able to copy jmeter-java-dsl-azure jar and its dependencies with dependency:copy-dependencies maven plugin goal
16 |
17 |
18 |
19 | us.abstracta.jmeter
20 | jmeter-java-dsl-azure
21 | ${jmeter-java-dsl.version}
22 |
23 |
24 | us.abstracta.jmeter
25 | jmeter-java-dsl
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/devbox.lock:
--------------------------------------------------------------------------------
1 | {
2 | "lockfile_version": "1",
3 | "packages": {
4 | "dotnet-sdk@latest": {
5 | "last_modified": "2023-09-10T10:53:27Z",
6 | "resolved": "github:NixOS/nixpkgs/78058d810644f5ed276804ce7ea9e82d92bee293#dotnet-sdk",
7 | "source": "devbox-search",
8 | "version": "6.0.413"
9 | },
10 | "maven@latest": {
11 | "last_modified": "2023-09-10T10:53:27Z",
12 | "resolved": "github:NixOS/nixpkgs/78058d810644f5ed276804ce7ea9e82d92bee293#maven",
13 | "source": "devbox-search",
14 | "version": "3.9.4"
15 | },
16 | "nodePackages.pnpm@latest": {
17 | "last_modified": "2023-09-10T10:53:27Z",
18 | "resolved": "github:NixOS/nixpkgs/78058d810644f5ed276804ce7ea9e82d92bee293#nodePackages.pnpm",
19 | "source": "devbox-search",
20 | "version": "8.6.12"
21 | },
22 | "nodejs@latest": {
23 | "last_modified": "2023-09-10T10:53:27Z",
24 | "plugin_version": "0.0.2",
25 | "resolved": "github:NixOS/nixpkgs/78058d810644f5ed276804ce7ea9e82d92bee293#nodejs_20",
26 | "source": "devbox-search",
27 | "version": "20.6.1"
28 | },
29 | "temurin-bin-8@latest": {
30 | "last_modified": "2023-09-10T10:53:27Z",
31 | "resolved": "github:NixOS/nixpkgs/78058d810644f5ed276804ce7ea9e82d92bee293#temurin-bin-8",
32 | "source": "devbox-search",
33 | "version": "8.0.372"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.BlazeMeter/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | us.abstracta.jmeter.dotnet
8 | jmeter-dotnet-dsl-parent
9 | 1.0-SNAPSHOT
10 | ../pom.xml
11 |
12 | jmeter-dotnet-dsl-blazemeter
13 | pom
14 |
15 | This pom is only needed to be able to copy jmeter-java-dsl-blazmeter jar and its dependencies with dependency:copy-dependencies maven plugin goal
16 |
17 |
18 |
19 | us.abstracta.jmeter
20 | jmeter-java-dsl-blazemeter
21 | ${jmeter-java-dsl.version}
22 |
23 |
24 | us.abstracta.jmeter
25 | jmeter-java-dsl
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/docs/guide/request-generation/loops/forloop-controller.md:
--------------------------------------------------------------------------------
1 | #### Iterating a fixed number of times
2 |
3 | In simple scenarios where you just want to execute a fixed number of times, within a thread group iteration, a given part of the test plan, you can just use `ForLoopController` (which uses [JMeter Loop Controller component](https://jmeter.apache.org/usermanual/component_reference.html#Loop_Controller)) as in the following example:
4 |
5 | ```cs
6 | using static Abstracta.JmeterDsl.JmeterDsl;
7 |
8 | public class PerformanceTest
9 | {
10 | [Test]
11 | public void LoadTest()
12 | {
13 | var stats = TestPlan(
14 | ThreadGroup(2, 10,
15 | ForLoopController(5,
16 | HttpSampler("http://my.service/accounts")
17 | )
18 | )
19 | ).Run();
20 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
21 | }
22 | }
23 | ```
24 |
25 | This will result in 10 * 5 = 50 requests to the given URL for each thread in the thread group.
26 |
27 | ::: tip
28 | JMeter automatically generates a variable `__jm____idx` with the current index of for loop iteration (starting with 0) which you can use in children elements. The default name for the for loop controller, when not specified, is `for`.
29 | :::
30 |
31 | Check [ForLoopController](/Abstracta.JmeterDsl/Core/Controllers/ForLoopController.cs) for more details.
32 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Samplers/BaseSampler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Abstracta.JmeterDsl.Core.TestElements;
3 | using Abstracta.JmeterDsl.Core.ThreadGroups;
4 |
5 | namespace Abstracta.JmeterDsl.Core.Samplers
6 | {
7 | ///
8 | /// Hosts common logic to all samplers.
9 | ///
10 | /// In particular, it specifies that samplers are and containing .
11 | /// For an example of an implementation of a sampler check
12 | ///
13 | public abstract class BaseSampler : TestElementContainer, IThreadGroupChild
14 | where T : BaseSampler
15 | {
16 | protected BaseSampler(string name)
17 | : base(name, Array.Empty())
18 | {
19 | }
20 |
21 | ///
22 | /// Allows specifying children test elements for the sampler, which allow for example extracting
23 | /// information from response, alter request, assert response contents, etc.
24 | ///
25 | /// list of test elements to add as children of this sampler.
26 | /// the altered sampler to allow for fluent API usage.
27 | public new T Children(params ISamplerChild[] children)
28 | => base.Children(children);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/docs/guide/response-processing/response-assertion.md:
--------------------------------------------------------------------------------
1 | ### Check for expected response
2 |
3 | By default, JMeter marks any HTTP request with a fail response code (4xx or 5xx) as failed, which allows you to easily identify when some request unexpectedly fails. But in many cases, this is not enough or desirable, and you need to check for the response body (or some other field) to contain (or not) a certain string.
4 |
5 | This is usually accomplished in JMeter with the usage of Response Assertions, which provides an easy and fast way to verify that you get the proper response for each step of the test plan, marking the request as a failure when the specified condition is not met.
6 |
7 | Here is an example of how to specify a response assertion in JMeter DSL:
8 |
9 | ```cs
10 | using static Abstracta.JmeterDsl.JmeterDsl;
11 |
12 | public class PerformanceTest
13 | {
14 | [Test]
15 | public void LoadTest()
16 | {
17 | var stats = TestPlan(
18 | ThreadGroup(2, 10,
19 | HttpSampler("http://my.service")
20 | .Children(
21 | ResponseAssertion().ContainsSubstrings("OK")
22 | )
23 | )
24 | ).Run();
25 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
26 | }
27 | }
28 | ```
29 |
30 | Check [Response Assertion](/Abstracta.JmeterDsl/Core/Assertions/DslResponseAssertion.cs) for more details and additional options.
31 |
--------------------------------------------------------------------------------
/docs/guide/protocols/http/parameters.md:
--------------------------------------------------------------------------------
1 | #### Parameters
2 |
3 | In many cases, you will need to specify some URL query string parameters or URL encoded form bodies. For these cases, you can use `Param` method as in the following example:
4 |
5 | ```cs
6 | using static Abstracta.JmeterDsl.JmeterDsl;
7 | using System.Net.Http;
8 |
9 | public class PerformanceTest
10 | {
11 | [Test]
12 | public void LoadTest()
13 | {
14 | var baseUrl = "https://myservice.com/products";
15 | TestPlan(
16 | ThreadGroup(1, 1,
17 | // GET https://myservice.com/products?name=iron+chair
18 | HttpSampler("GetIronChair", baseUrl)
19 | .Param("name", "iron chair"),
20 | /*
21 | * POST https://myservice.com/products
22 | * Content-Type: application/x-www-form-urlencoded
23 | *
24 | * name=wooden+chair
25 | */
26 | HttpSampler("CreateWoodenChair", baseUrl)
27 | .Method(HttpMethod.Post.Method) // POST
28 | .Param("name", "wooden chair")
29 | )
30 | ).Run();
31 | }
32 | }
33 | ```
34 |
35 | ::: tip
36 | JMeter automatically URL encodes parameters, so you don't need to worry about special characters in parameter names or values.
37 |
38 | If you want to use some custom encoding or have an already encoded value that you want to use, then you can use `RawParam` method instead which does not apply any encoding to the parameter name or value, and send it as is.
39 | :::
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Listeners/ResultsTreeVisualizer.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.Listeners
2 | {
3 | ///
4 | /// Shows a popup window including live results tree using JMeter built-in View Results Tree
5 | /// element.
6 | ///
7 | /// If resultsTreeVisualizer is added at testPlan level it will show information about all samples in
8 | /// the test plan, if added at thread group level it will only show samples for samplers contained
9 | /// within it, if added as a sampler child, then only that sampler samples will be shown.
10 | ///
11 | public class ResultsTreeVisualizer : BaseListener
12 | {
13 | protected int? _resultsLimit;
14 |
15 | ///
16 | /// Specifies the maximum number of sample results to show.
17 | ///
18 | /// When the limit is reached, only latest sample results are shown.
19 | ///
20 | /// Take into consideration that the greater the number of displayed results, the more system
21 | /// memory is required, which might cause an OutOfMemoryError depending on JVM settings.
22 | ///
23 | /// the maximum number of sample results to show. When not set the default
24 | /// value is 500.
25 | /// the visualizer for further configuration or usage.
26 | public ResultsTreeVisualizer ResultsLimit(int resultsLimit)
27 | {
28 | _resultsLimit = resultsLimit;
29 | return this;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/docs/guide/debugging/debug-jmeter.md:
--------------------------------------------------------------------------------
1 | ### Debug JMeter code
2 |
3 | You can even add breakpoints to JMeter or JMeter Java DSL code in your IDE and debug the code line by line providing the greatest possible detail.
4 |
5 | Here is an example screenshot debugging HTTP Sampler:
6 |
7 | 
8 |
9 | For that, you need to:
10 |
11 | * have a Java IDE with JMeter or JMeter Java DSL code open.
12 | * set proper breakpoints in the code you are interested in debugging.
13 | * can configure Remote JVM Debug like in the following screenshot:
14 | 
15 | * set required JVM arguments in the JMeter .Net DSL test using `EmbeddedJmeterEngine` like in the following example:
16 | ```cs
17 | TestPlan(
18 | ThreadGroup(threads: 1, iterations: 1,
19 | HttpSampler("http://my.service")
20 | )
21 | ).RunIn(new EmbeddedJmeterEngine()
22 | .JvmArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"));
23 | ```
24 | > Note that we changed the `suspend` flag to `y` to block test execution until Remote JVM Debug is run in IDE.
25 | * run the JMeter .Net DSL test. The test should get stuck until you start Remote JVM Debug in the Java IDE.
26 | * start the Remote JVM Devug in the Java IDE.
27 | * wait for a breakpoint to activate and debug as usual 🙂.
28 |
29 | ::: tip
30 | JMeter class in charge of executing threads logic is `org.apache.jmeter.threads.JMeterThread`. You can check the classes used by each DSL-provided test element by checking the Java DSL code.
31 | :::
32 |
--------------------------------------------------------------------------------
/docs/guide/debugging/dummy-sampler.md:
--------------------------------------------------------------------------------
1 | ### Dummy sampler
2 |
3 | In many cases, you want to be able to test part of the test plan but without directly interacting with the service under test, avoiding any potential traffic to the servers, testing some border cases which might be difficult to reproduce with the actual server, and avoid actual server interactions variability and potential unpredictability. In such scenarios, you might replace actual samplers with `DummySampler` (which uses [Dummy Sampler plugin](https://jmeter-plugins.org/wiki/DummySampler/)) to be able to test extractors, assertions, controllers conditions, and other parts of the test plan under certain conditions/results generated by the samplers.
4 |
5 | Here is an example:
6 |
7 | ```cs
8 | using static Abstracta.JmeterDsl.JmeterDsl;
9 |
10 | public class PerformanceTest
11 | {
12 | [Test]
13 | public void LoadTest()
14 | {
15 | TestPlan(
16 | ThreadGroup(2, 10,
17 | // HttpSampler("http://my.service")
18 | DummySampler("{\"status\" : \"OK\"}")
19 | )
20 | ).Run();
21 | }
22 | }
23 | ```
24 |
25 | ::: tip
26 | The DSL configures dummy samplers by default, in contrast to what JMeter does, with response time simulation disabled. This allows to speed up the debugging process, not having to wait for proper response time simulation (sleeps/waits). If you want a more accurate emulation, you might turn it on through the `ResponseTimeSimulation()` method.
27 | :::
28 |
29 | Check [DslDummySampler](/Abstracta.JmeterDsl/Core/Samplers/DslDummySampler.cs) for more information o additional configuration and options.
30 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroHeight: 68
4 | heroImage: /logo.svg
5 | actions:
6 | - text: User Guide →
7 | link: /guide/
8 | features:
9 | - title: 💙 Git, IDE & Programmers Friendly
10 | details: Simple way of defining performance tests that takes advantage of IDEs autocompletion and inline documentation.
11 | - title: 💪 JMeter ecosystem & community
12 | details: Use the most popular performance tool and take advantage of the wide support of protocols and tools.
13 | - title: 😎 Built-in features & extensibility
14 | details: Built-in additional features which ease usage and using it in CI/CD pipelines.
15 | footer: Made by Abstracta with ❤️ | Apache 2.0 Licensed | Powered by Vuepress
16 | footerHtml: true
17 | ---
18 |
19 | ## Example
20 |
21 | Add the package to your project:
22 |
23 | ```powershell
24 | dotnet add package Abstracta.JmeterDsl --version 0.8
25 | ```
26 |
27 | Create performance test:
28 |
29 | ```cs
30 | using static Abstracta.JmeterDsl.JmeterDsl;
31 |
32 | public class PerformanceTest
33 | {
34 | [Test]
35 | public void LoadTest()
36 | {
37 | var stats = TestPlan(
38 | ThreadGroup(2, 10,
39 | HttpSampler("http://my.service")
40 | )
41 | ).Run();
42 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
43 | }
44 | }
45 | ```
46 |
47 | > **Java 8+ is required** for test plan execution.
48 |
49 | [Here](https://github.com/abstracta/jmeter-dotnet-dsl-sample) is a sample project in case you want to start one from scratch.
50 |
51 |
--------------------------------------------------------------------------------
/docs/guide/request-generation/jsr223-pre-processor.md:
--------------------------------------------------------------------------------
1 | ### Provide request parameters programmatically per request
2 |
3 | So far we have seen a how to generate requests with information extracted from CSV, but this is not enough for some scenarios. When you need more flexibility and power you can use `jsr223preProcessor` to specify your own logic to build each request.
4 |
5 | Here is an example:
6 |
7 | ```cs
8 | using System;
9 | using System.Net.Http.Headers;
10 | using System.Net.Mime;
11 | using static Abstracta.JmeterDsl.JmeterDsl;
12 |
13 | public class PerformanceTest
14 | {
15 | [Test]
16 | public void LoadTest()
17 | {
18 | var stats = TestPlan(
19 | ThreadGroup(5, 10,
20 | HttpSampler("http://my.service")
21 | .Post("${REQUEST_BODY}", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain))
22 | .Children(Jsr223PreProcessor("vars.put('REQUEST_BODY', '{\"time\": \"' + Instant.now() + '\"}')"))
23 | )
24 | ).Run();
25 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
26 | }
27 | }
28 | ```
29 |
30 | ::: tip
31 | For the time being only JSR223 scripts can be used. By default `Groovy` is used, but you can change to others by using the provided `Language()` method.
32 |
33 | We plan in the future to look for alternatives as to be able to use .Net code as pre processor. If you are interested in this you can let us know in [this issue](https://github.com/abstracta/jmeter-dotnet-dsl/issues/3) posting your use case.
34 | :::
35 |
36 | Check [DslJsr223PreProcessor](/Abstracta.JmeterDsl/Core/PreProcessors/DslJsr223PreProcessor.cs) for more details and additional options.
37 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/DslTestPlan.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Bridge;
2 | using Abstracta.JmeterDsl.Core.Engines;
3 | using Abstracta.JmeterDsl.Core.TestElements;
4 |
5 | namespace Abstracta.JmeterDsl.Core
6 | {
7 | ///
8 | /// Represents a JMeter test plan, with associated thread groups and other children elements.
9 | ///
10 | public class DslTestPlan : TestElementContainer
11 | {
12 | public DslTestPlan(ITestPlanChild[] children)
13 | : base(null, children)
14 | {
15 | }
16 |
17 | ///
18 | /// Uses to run the test plan.
19 | ///
20 | /// A object containing all statistics of the test plan execution.
21 | public TestPlanStats Run() =>
22 | new EmbeddedJmeterEngine().Run(this);
23 |
24 | ///
25 | /// Allows to run the test plan in a given engine.
26 | ///
27 | /// This method is just a simple method which provides fluent API to run the test plans in a given
28 | /// engine.
29 | ///
30 | ///
31 | public TestPlanStats RunIn(IDslJmeterEngine engine) =>
32 | engine.Run(this);
33 |
34 | ///
35 | /// Saves the given test plan as JMX, which allows it to be loaded in JMeter GUI.
36 | ///
37 | /// specifies where to store the JMX of the test plan.
38 | public void SaveAsJmx(string filePath) =>
39 | new BridgeService().SaveTestPlanAsJmx(this, filePath);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | # User guide
2 |
3 | Here we share some tips and examples on how to use the DSL to tackle common use cases.
4 |
5 | Provided examples use [Nunit](https://nunit.org/), but you can use other test libraries.
6 |
7 | Explore the DSL in your preferred IDE to discover all available features, and consider reviewing [existing tests](/Abstracta.JmeterDsl.Tests) for additional examples.
8 |
9 | The .Net DSL currently does not support all use cases supported by the [Java Dsl](https://abstracta.github.io/jmeter-java-dsl/), and currently only focuses on a limited set of features that cover the most commonly used cases. If you identify any particular scenario (or JMeter feature) that you need and is not currently supported, or easy to use, **please let us know by [creating an issue](https://github.com/abstracta/jmeter-dotnet-dsl/issues)** and we will try to implement it as soon as possible. Usually porting JMeter features is quite fast, and porting existing Java DSL features is even faster.
10 |
11 | ::: tip
12 | If you like this project, **please give it a star ⭐ in [GitHub](https://github.com/abstracta/jmeter-dotnet-dsl)!**. This helps the project be more visible, gain relevance and encourages us to invest more effort in new features.
13 | :::
14 |
15 | For an intro to JMeter concepts and components, you can check [JMeter official documentation](http://jmeter.apache.org/usermanual/get-started.html).
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --c-brand: #a502ce;
3 | --c-brand-light: #b55ecb;
4 |
5 | --c-shadow: rgba(0,0,0,0.2);
6 | }
7 |
8 | html.dark {
9 | // brand colors
10 | --c-brand: #b55ecb;
11 | --c-brand-light: #a502ce;
12 |
13 | --c-shadow: rgba(255,255,255,0.2);
14 | }
15 |
16 | /* This is a fix to avoid, when building (does not happen with dev server),
17 | showing two external link icons for external links containing icons.*/
18 | a.external-link>svg~span:nth-child(2) {
19 | display: none;
20 | }
21 |
22 | .vertical-divider {
23 | display: inline-flex;
24 | width:0;
25 | border: solid;
26 | border-width: 0 thin 0 0;
27 | border-color: var(--c-border);
28 | min-height: 80%;
29 | vertical-align: text-bottom;
30 | margin: 0 0.5rem;
31 | }
32 |
33 | .hero-logo {
34 | margin: 3rem auto 1.5rem;
35 | display: flex;
36 | align-items: center;
37 | justify-content: center;
38 | }
39 |
40 | .hero-logo span {
41 | font-weight:500;
42 | font-size: 3rem;
43 | color: var(--c-text-accent);
44 | margin: 0 0 0 0.7rem;
45 | }
46 |
47 | .grid {
48 | display: flex;
49 | }
50 |
51 | .grid-logo {
52 | display: flex;
53 | align-items: center;
54 | width: 25%;
55 | height: 100px;
56 | margin-right: 10px;
57 | padding: 10px;
58 | border: 1px solid var(--c-border);
59 | border-radius: 5px;
60 | box-shadow: 0 4px 8px 0 var(--c-shadow);
61 | }
62 |
63 | .grid-logo a {
64 | width: 100%;
65 | }
66 |
67 | .grid-logo img {
68 | width: 100%;
69 | -webkit-filter: drop-shadow(1px 0 0 white)
70 | drop-shadow(0 1px 0 white)
71 | drop-shadow(-1px 0 0 white)
72 | drop-shadow(0 -1px 0 white);
73 | -filter: drop-shadow(1px 0 0 white)
74 | drop-shadow(0 1px 0 white)
75 | drop-shadow(-1px 0 0 white)
76 | drop-shadow(0 -1px 0 white);
77 | }
78 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Stats/TimeMetricSummary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Stats
4 | {
5 | ///
6 | /// Provides summary data for a set of timing values.
7 | ///
8 | public class TimeMetricSummary
9 | {
10 | ///
11 | /// Gets the minimum collected value.
12 | ///
13 | public TimeSpan Min { get; set; }
14 |
15 | ///
16 | /// Gets the maximum collected value.
17 | ///
18 | public TimeSpan Max { get; set; }
19 |
20 | ///
21 | /// Gets the mean/average of collected values.
22 | ///
23 | public TimeSpan Mean { get; set; }
24 |
25 | ///
26 | /// Gets the median of collected values.
27 | ///
28 | /// The median is the same as percentile 50, and is the value for which 50% of the collected values
29 | /// is smaller/greater.
30 | /// This value might differ from when distribution of values is not symmetric.
31 | ///
32 | public TimeSpan Median { get; set; }
33 |
34 | ///
35 | /// Gets the 90 percentile of samples times.
36 | ///
37 | /// 90% of samples took less or equal to the returned value.
38 | ///
39 | public TimeSpan Perc90 { get; set; }
40 |
41 | ///
42 | /// Gets the 95 percentile of samples times.
43 | ///
44 | /// 95% of samples took less or equal to the returned value.
45 | ///
46 | public TimeSpan Perc95 { get; set; }
47 |
48 | ///
49 | /// Gets the 99 percentile of samples times.
50 | ///
51 | /// 99% of samples took less or equal to the returned value.
52 | ///
53 | public TimeSpan Perc99 { get; set; }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/docs/guide/debugging/view-results-tree.md:
--------------------------------------------------------------------------------
1 | ### View results tree
2 |
3 | One option is using provided `ResultsTreeVisualizer()` like in the following example:
4 |
5 | ```cs
6 | using static Abstracta.JmeterDsl.JmeterDsl;
7 |
8 | public class PerformanceTest
9 | {
10 | [Test]
11 | public void LoadTest()
12 | {
13 | var stats = TestPlan(
14 | ThreadGroup(2, 10,
15 | HttpSampler("http://my.service")
16 | ),
17 | ResultsTreeVisualizer()
18 | ).Run();
19 | }
20 | }
21 | ```
22 |
23 | This will display the JMeter built-in View Results Tree element, which allows you to review request and response contents in addition to collected metrics (spent time, sent & received bytes, etc.) for each request sent to the server, in a window like this one:
24 |
25 | 
26 |
27 | ::: tip
28 | To debug test plans use a few iterations and threads to reduce the execution time and ease tracing by having less information to analyze.
29 | :::
30 |
31 | ::: tip
32 | When adding `ResultsTreeVisualizer()` as a child of a thread group, it will only display sample results of that thread group. When added as a child of a sampler, it will only show sample results for that sampler. You can use this to only review certain sample results in your test plan.
33 | :::
34 |
35 | ::: tip
36 | **Remove `ResultsTreeVisualizer()` from test plans when are no longer needed** (when debugging is finished). Leaving them might interfere with unattended test plan execution (eg: in CI) due to test plan execution not finishing until all visualizers windows are closed.
37 | :::
38 |
39 | ::: warning
40 | By default, View Results Tree only displays the last 500 sample results. If you need to display more elements, use provided `ResultsLimit(int)` method which allows changing this value. Take into consideration that the more results are shown, the more memory that will require. So use this setting with care.
41 | :::
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Http/DslHttpCookies.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Configs;
2 |
3 | namespace Abstracta.JmeterDsl.Http
4 | {
5 | ///
6 | /// Allows configuring cookies settings used by HTTP samplers.
7 | ///
8 | /// This element can only be added as child of test plan, and currently allows only to disable HTTP
9 | /// cookies handling which is enabled by default (emulating browser behavior).
10 | ///
11 | /// This element has to be added before any http sampler to be considered, and if you add multiple
12 | /// instances of cookie manager to a test plan, only the first one will be considered.
13 | ///
14 | public class DslHttpCookies : BaseConfigElement
15 | {
16 | protected bool? _disable;
17 | protected bool? _clearCookiesBetweenIterations;
18 |
19 | public DslHttpCookies()
20 | : base(null)
21 | {
22 | }
23 |
24 | ///
25 | /// Disables HTTP cookies handling for the test plan.
26 | ///
27 | /// the DslHttpCookies to allow fluent API usage.
28 | public DslHttpCookies Disable()
29 | {
30 | _disable = true;
31 | return this;
32 | }
33 |
34 | ///
35 | /// Allows to enable or disable clearing cookies between thread group iterations.
36 | ///
37 | /// Cookies are cleared each iteration by default. If this is not desirable, for instance if
38 | /// logging in once and then iterating through actions multiple times, use this to set to false.
39 | ///
40 | /// clear boolean to set clearing of cookies. By default, it is set to true.
41 | /// the DslHttpCookies for further configuration or usage.
42 | public DslHttpCookies ClearCookiesBetweenIterations(bool clear)
43 | {
44 | _clearCookiesBetweenIterations = clear;
45 | return this;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Abstracta.JmeterDsl.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | Abstracta.JmeterDsl
5 | false
6 | ../StyleCop.ruleset
7 | true
8 |
9 |
10 |
11 |
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 | all
14 |
15 |
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 | all
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | PreserveNewest
34 |
35 |
36 | PreserveNewest
37 |
38 |
39 | PreserveNewest
40 |
41 |
42 |
--------------------------------------------------------------------------------
/docs/guide/thread-groups/ramps-and-holds.md:
--------------------------------------------------------------------------------
1 | ### Thread ramps and holds
2 |
3 | When working with many threads, it is advisable to configure a ramp-up period, to avoid starting all threads at once affecting performance metrics and generation.
4 |
5 | You can easily configure a ramp-up with the DSL like this:
6 |
7 | ```cs
8 | ThreadGroup().RampTo(10, TimeSpan.FromSeconds(5)).HoldIterating(20) // ramp to 10 threads for 5 seconds (1 thread every half second) and iterating each thread 20 times
9 | ThreadGroup().RampToAndHold(10, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)) //similar as above but after ramping up holding execution for 20 seconds
10 | ```
11 |
12 | Additionally, you can use and combine these same methods to configure more complex scenarios (incremental, peak, and any other types of tests) like the following one:
13 |
14 | ```cs
15 | ThreadGroup()
16 | .RampToAndHold(10, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20))
17 | .RampToAndHold(100, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30))
18 | .RampTo(200, TimeSpan.FromSeconds(10))
19 | .RampToAndHold(100, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30))
20 | .RampTo(0, TimeSpan.FromSeconds(5))
21 | .Children(
22 | HttpSampler("http://my.service")
23 | )
24 | ```
25 |
26 | Which would translate into the following threads' timeline:
27 |
28 | 
29 |
30 | Check [DslThreadGroup](/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/threadgroups/DslThreadGroup.java) for more details.
31 |
32 | ::: tip
33 | If you are a JMeter GUI user, you may even be interested in using provided `TestElement.ShowInGui()` method, which shows the JMeter test element GUI that could help you understand what will DSL execute in JMeter. You can use this method with any test element generated by the DSL (not just thread groups).
34 |
35 | For example, for the above test plan you would get a window like the following one:
36 |
37 | 
38 | :::
39 |
40 | ::: tip
41 | When using multiple thread groups in a test plan, consider setting a name (eg: `ThreadGroup("main", 1, 1, ...)`) on them to properly identify associated requests in statistics & jtl results.
42 | :::
43 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Azure/Abstracta.JmeterDsl.Azure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Abstracta.JmeterDsl.Azure
6 | ../StyleCop.ruleset
7 | true
8 | true
9 | Abstracta.JmeterDsl.Azure
10 | Abstracta.JmeterDsl.Azure
11 | Abstracta
12 | Abstracta
13 | Module which allows to easily run Abstracta.JmeterDsl test plans at scale in Azure Load Testing.
14 | true
15 | jmeter,performance,load,test
16 | logo.png
17 | Apache-2.0
18 | https://abstracta.github.io/jmeter-dotnet-dsl
19 | README.md
20 | true
21 | snupkg
22 | true
23 | 0.9.0.0
24 | 0.9-alpha1
25 | 0.9
26 |
27 |
28 | true
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.BlazeMeter/Abstracta.JmeterDsl.BlazeMeter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Abstracta.JmeterDsl.BlazeMeter
6 | ../StyleCop.ruleset
7 | true
8 | true
9 | Abstracta.JmeterDsl.BlazeMeter
10 | Abstracta.JmeterDsl.BlazeMeter
11 | Abstracta
12 | Abstracta
13 | Module which allows to easily run Abstracta.JmeterDsl test plans at scale in BlazeMeter.
14 | true
15 | jmeter,performance,load,test
16 | logo.png
17 | Apache-2.0
18 | https://abstracta.github.io/jmeter-dotnet-dsl
19 | README.md
20 | true
21 | snupkg
22 | true
23 | 0.9.0.0
24 | 0.9-alpha1
25 | 0.9
26 |
27 |
28 |
29 | true
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/NavbarBrand.vue:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 | .net
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/docs/guide/reporting/logging.md:
--------------------------------------------------------------------------------
1 | ### Log requests and responses
2 |
3 | The main mechanism provided by JMeter (and `Abstracta.JmeterDsl`) to get information about generated requests, responses, and associated metrics is through the generation of JTL files.
4 |
5 | This can be easily achieved by using provided `JtlWriter` like in this example:
6 |
7 | ```cs
8 | using static Abstracta.JmeterDsl.JmeterDsl;
9 |
10 | public class PerformanceTest
11 | {
12 | [Test]
13 | public void LoadTest()
14 | {
15 | var stats = TestPlan(
16 | ThreadGroup(2, 10,
17 | HttpSampler("http://my.service")
18 | ),
19 | JtlWriter("jtls")
20 | ).Run();
21 | }
22 | }
23 | ```
24 |
25 | ::: tip
26 | By default, `JtlWriter` will write the most used information to evaluate the performance of the tested service. If you want to trace all the information of each request you may use `JtlWriter` with the `WithAllFields()` option. Doing this will provide all the information at the cost of additional computation and resource usage (fewer resources for actual load testing). You can tune which fields to include or not with `JtlWriter` and only log what you need, check [JtlWriter](/Abstracta.JmeterDsl/Core/Listeners/JtlWriter.cs) for more details.
27 | :::
28 |
29 | ::: tip
30 | `JtlWriter` will automatically generate `.jtl` files applying this format: `.jtl`.
31 |
32 | If you need a specific file name, for example for later postprocessing logic (eg: using CI build ID), you can specify it by using `JtlWriter(directory, fileName)`.
33 |
34 | When specifying the file name, make sure to use unique names, otherwise, the JTL contents may be appended to previous existing jtl files.
35 | :::
36 |
37 | An additional option, specially targeted towards logging sample responses, is `ResponseFileSaver` which automatically generates a file for each received response. Here is an example:
38 |
39 | ```cs
40 | using static Abstracta.JmeterDsl.JmeterDsl;
41 |
42 | public class PerformanceTest
43 | {
44 | [Test]
45 | public void LoadTest()
46 | {
47 | TestPlan(
48 | ThreadGroup(2, 10,
49 | HttpSampler("http://my.service")
50 | ),
51 | ResponseFileSaver(DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss").Replace(":", "-") + "-response")
52 | ).Run();
53 | }
54 | }
55 | ```
56 |
57 | Check [ResponseFileSaver](/Abstracta.JmeterDsl/Core/Listeners/ResponseFileSaver.cs) for more details.
58 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Http/HttpHeaders.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Http.Headers;
3 | using Abstracta.JmeterDsl.Core.TestElements;
4 |
5 | namespace Abstracta.JmeterDsl.Http
6 | {
7 | ///
8 | /// Allows specifying HTTP headers (through an underlying JMeter HttpHeaderManager) to be used by
9 | /// HTTP samplers.
10 | ///
11 | /// This test element can be added at different levels (in the same way as HTTPHeaderManager) of a
12 | /// test plan affecting all samplers in the scope were is added.For example if httpHeaders is
13 | /// specified at test plan, then all headers will apply to http samplers; if it is specified on
14 | /// thread group, then only samplers on that thread group would be affected; if specified as a child
15 | /// of a sampler, only the particular sampler will include such headers.Also take into consideration
16 | /// that headers specified at lower scope will overwrite ones specified at higher scope (eg: sampler
17 | /// child headers will overwrite test plan headers).
18 | ///
19 | public class HttpHeaders : BaseTestElement, IMultiLevelTestElement
20 | {
21 | private readonly Dictionary _headers = new Dictionary();
22 |
23 | public HttpHeaders()
24 | : base(null)
25 | {
26 | }
27 |
28 | ///
29 | /// Allows to set an HTTP header to be used by HTTP samplers.
30 | ///
31 | /// To specify multiple headers just invoke this method several times with the different header
32 | /// names and values.
33 | ///
34 | /// specifies name of the HTTP header.
35 | /// specifies value of the HTTP header.
36 | /// the config element for further configuration or usage.
37 | public HttpHeaders Header(string name, string value)
38 | {
39 | _headers[name] = value;
40 | return this;
41 | }
42 |
43 | ///
44 | /// Allows to easily specify the Content-Type HTTP header.
45 | ///
46 | /// value to use as Content-Type header.
47 | /// the config element for further configuration or usage.
48 | public HttpHeaders ContentType(MediaTypeHeaderValue value)
49 | {
50 | _headers["Content-Type"] = value.ToString();
51 | return this;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Stats/StatsSummary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Abstracta.JmeterDsl.Core.Stats
4 | {
5 | ///
6 | /// Contains summary statistics of a group of collected sample results.
7 | ///
8 | public class StatsSummary
9 | {
10 | ///
11 | /// Gets the instant when the first sample started.
12 | ///
13 | /// When associated to a test plan or transaction it gets its start time.
14 | ///
15 | public DateTime FirstTime { get; set; }
16 |
17 | ///
18 | /// Gets the instant when the last sample ended.
19 | ///
20 | /// When associated to a test plan or transaction it gets its end time.
21 | ///
22 | /// Take into consideration that for transactions this time takes not only into consideration the
23 | /// endTime of last sample, but also the time spent in timers and pre and postprocessors.
24 | ///
25 | public DateTime EndTime { get; set; }
26 |
27 | ///
28 | /// Gets metrics for number of samples
29 | ///
30 | /// This counts both failing and passing samples.
31 | ///
32 | public CountMetricSummary Samples { get; set; }
33 |
34 | ///
35 | /// Gets the total number of samples.
36 | ///
37 | public long SamplesCount => Samples.Total;
38 |
39 | ///
40 | /// Gets metrics for number of samples that failed.
41 | ///
42 | public CountMetricSummary Errors { get; set; }
43 |
44 | ///
45 | /// Gets the total number of samples that failed.
46 | ///
47 | public long ErrorsCount => Errors.Total;
48 |
49 | ///
50 | /// Gets metrics for time spent in samples.
51 | ///
52 | public TimeMetricSummary SampleTime { get; set; }
53 |
54 | ///
55 | /// Gets the 99 percentile of samples times.
56 | ///
57 | /// 99% of samples took less or equal to the returned value.
58 | ///
59 | public TimeSpan SampleTimePercentile99 => SampleTime.Perc99;
60 |
61 | ///
62 | /// Gets metrics for received bytes in sample responses.
63 | ///
64 | public CountMetricSummary ReceivedBytes { get; set; }
65 |
66 | ///
67 | /// Gets metrics for sent bytes in samples requests.
68 | ///
69 | public CountMetricSummary SentBytes { get; set; }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/TestElements/DslScopedTestElement.cs:
--------------------------------------------------------------------------------
1 | namespace Abstracta.JmeterDsl.Core.TestElements
2 | {
3 | ///
4 | /// Contains common logic for test elements that only process certain samples.
5 | ///
6 | /// is the type of the test element that extends this class (to properly inherit fluent
7 | /// API methods).
8 | public abstract class DslScopedTestElement : BaseTestElement, IMultiLevelTestElement
9 | where T : DslScopedTestElement
10 | {
11 | protected DslScope? _scope;
12 | protected string _scopeVariable;
13 |
14 | protected DslScopedTestElement(string name)
15 | : base(name)
16 | {
17 | }
18 |
19 | public enum DslScope
20 | {
21 | ///
22 | /// Applies the regular extractor to all samples (main and sub samples).
23 | ///
24 | AllSamples,
25 |
26 | ///
27 | /// Applies the regular extractor only to main sample (sub samples, like redirects, are not
28 | /// included).
29 | ///
30 | MainSample,
31 |
32 | ///
33 | /// Applies the regular extractor only to sub samples (redirects, embedded resources, etc.).
34 | ///
35 | SubSamples,
36 | }
37 |
38 | ///
39 | /// Allows specifying if the element should be applied to main sample and/or sub samples.
40 | ///
41 | /// When not specified the element will only apply to main sample.
42 | ///
43 | /// specifying to what sample result apply the element to.
44 | /// the DSL element for further configuration or usage.
45 | ///
46 | public T Scope(DslScope scope)
47 | {
48 | _scope = scope;
49 | return (T)this;
50 | }
51 |
52 | ///
53 | /// Allows specifying that the element should be applied to the contents of a given JMeter
54 | /// variable.
55 | ///
56 | /// This setting overrides any setting on scope and fieldToCheck.
57 | ///
58 | /// specifies the name of the variable to apply the element to.
59 | /// the DSL element for further configuration or usage.
60 | public T ScopeVariable(string scopeVariable)
61 | {
62 | _scopeVariable = scopeVariable;
63 | return (T)this;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl.Tests/Core/PostProcessors/DslRegexExtractorTest.cs:
--------------------------------------------------------------------------------
1 | using WireMock.FluentAssertions;
2 | using WireMock.RequestBuilders;
3 | using WireMock.ResponseBuilders;
4 | using WireMock.Server;
5 |
6 | namespace Abstracta.JmeterDsl.Core.PostProcessors
7 | {
8 | using static JmeterDsl;
9 |
10 | public class DslRegexExtractorTest
11 | {
12 | private WireMockServer _wiremock;
13 |
14 | [SetUp]
15 | public void SetUp()
16 | {
17 | _wiremock = WireMockServer.Start();
18 | _wiremock.Given(Request.Create().WithPath("/"))
19 | .RespondWith(Response.Create()
20 | .WithStatusCode(200));
21 | }
22 |
23 | [TearDown]
24 | public void TearDown() =>
25 | _wiremock.Stop();
26 |
27 | [Test]
28 | public void ShouldExtractVariableWhenSimpleRegexExtractorMatches()
29 | {
30 | var user = "test";
31 | var userParam = "user=";
32 | var userVar = "USER";
33 | TestPlan(
34 | ThreadGroup(1, 1,
35 | DummySampler(userParam + user)
36 | .Children(
37 | RegexExtractor(userVar, userParam + "(.*)")
38 | ),
39 | HttpSampler(_wiremock.Url + "/?" + userParam + "${" + userVar + "}")
40 | )).Run();
41 | _wiremock.Should().HaveReceivedACall().AtUrl(_wiremock.Url + "/?" + userParam + user);
42 | }
43 |
44 | [Test]
45 | public void ShouldExtractVariableWhenComplexRegexExtractorMatches()
46 | {
47 | var user = "test";
48 | var userParam = "user=";
49 | var userVar = "USER";
50 | TestPlan(
51 | ThreadGroup(1, 1,
52 | DummySampler("OK")
53 | .Url("http://localhost/?" + userParam + "user2&" + userParam + user)
54 | .Children(
55 | RegexExtractor(userVar, "([^&?]+)=([^&]+)")
56 | .MatchNumber(2)
57 | .Template("$2$")
58 | .FieldToCheck(DslRegexExtractor.DslTargetField.RequestUrl)
59 | .Scope(TestElements.DslScopedTestElement.DslScope.AllSamples)
60 | ),
61 | HttpSampler(_wiremock.Url + "/?" + userParam + "${" + userVar + "}")
62 | )).Run();
63 | _wiremock.Should().HaveReceivedACall().AtUrl(_wiremock.Url + "/?" + userParam + user);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Bridge/TimeSpanConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using YamlDotNet.Core;
3 | using YamlDotNet.Core.Events;
4 | using YamlDotNet.Serialization;
5 |
6 | namespace Abstracta.JmeterDsl.Core.Bridge
7 | {
8 | public class TimespanConverter : IYamlTypeConverter
9 | {
10 | private const string DurationPrefix = "PT";
11 |
12 | public bool Accepts(Type type) =>
13 | type == typeof(TimeSpan);
14 |
15 | public object ReadYaml(IParser parser, Type type)
16 | {
17 | var scalar = parser.Consume();
18 | var value = scalar.Value;
19 | if (!value.StartsWith(DurationPrefix))
20 | {
21 | throw new YamlException(scalar.Start, scalar.End, $"No valid duration value '{value}'");
22 | }
23 | int hours = 0, minutes = 0, seconds = 0, millis = 0;
24 | var lastPos = DurationPrefix.Length;
25 | var unitPos = value.IndexOf('H');
26 | if (unitPos >= 0)
27 | {
28 | hours = int.Parse(value.Substring(lastPos, unitPos - lastPos));
29 | lastPos = unitPos + 1;
30 | }
31 | unitPos = value.IndexOf('M');
32 | if (unitPos >= 0)
33 | {
34 | minutes = int.Parse(value.Substring(lastPos, unitPos - lastPos));
35 | lastPos = unitPos + 1;
36 | }
37 | unitPos = value.IndexOf('.');
38 | if (unitPos >= 0)
39 | {
40 | seconds = int.Parse(value.Substring(lastPos, unitPos - lastPos));
41 | lastPos = unitPos + 1;
42 | var millisStr = value.Substring(lastPos, Math.Min(3, value.Length - 1 - lastPos));
43 | millis = int.Parse(millisStr.PadLeft(3, '0'));
44 | }
45 | else if (value.Contains("S"))
46 | {
47 | seconds = int.Parse(value.Substring(lastPos, value.Length - lastPos - 1));
48 | }
49 | return new TimeSpan(0, hours, minutes, seconds, millis);
50 | }
51 |
52 | public void WriteYaml(IEmitter emitter, object value, Type type)
53 | {
54 | var ret = "PT";
55 | var duration = (TimeSpan)value;
56 | if (duration.Hours > 0)
57 | {
58 | ret += duration.Hours + "H";
59 | }
60 | if (duration.Minutes > 0)
61 | {
62 | ret += duration.Minutes + "M";
63 | }
64 | if ((duration.Hours == 0 && duration.Minutes == 0)
65 | || duration.Seconds > 0 || duration.Milliseconds > 0)
66 | {
67 | ret += duration.Seconds;
68 | if (duration.Milliseconds > 0)
69 | {
70 | ret += "." + duration.Milliseconds.ToString().PadLeft(3, '0');
71 | }
72 | ret += "S";
73 | }
74 | emitter.Emit(new Scalar(ret));
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/HomeHero.vue:
--------------------------------------------------------------------------------
1 |
75 |
76 |
77 |
78 |
.net
79 |
80 |
81 |
82 | Simple performance tests .Net API
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Abstracta.JmeterDsl.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | Abstracta.JmeterDsl
5 | ../StyleCop.ruleset
6 | true
7 | true
8 | Abstracta.JmeterDsl
9 | Abstracta.JmeterDsl
10 | Abstracta
11 | Abstracta
12 | Simple API to run JMeter performance tests in an VCS and programmers friendly way.
13 | true
14 | jmeter,performance,load,test
15 | logo.png
16 | Apache-2.0
17 | https://abstracta.github.io/jmeter-dotnet-dsl
18 | README.md
19 | true
20 | snupkg
21 | true
22 | 0.9.0.0
23 | 0.9-alpha1
24 | 0.9
25 |
26 |
27 | true
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | runtime; build; native; contentfiles; analyzers; buildtransitive
55 | all
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/docs/support/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar: false
3 | ---
4 |
5 | # Support
6 |
7 | ## Community Support
8 |
9 | The JMeter DSL project has a vibrant and active community that provides extensive support, on a best effort basis, to its users. Community support is primarily offered through the following channels:
10 |
11 | * [Discord server]: Join our [Discord server] to engage with fellow JMeter DSL enthusiasts. It's a real-time platform where you can ask questions, share experiences, and participate in discussions.
12 | * [GitHub Issues]: For bug reports, feature requests, or any specific problems you encounter while using JMeter DSL, [GitHub Issues] is the place to go. Create an issue, and the community will jump in to assist you, propose improvements, and collaborate on finding solutions.
13 | * [GitHub Discussions]: If you have open-ended discussions, ideas, or suggestions related to JMeter DSL, head over to [GitHub Discussions]. It's an excellent platform for brainstorming, gathering feedback, and engaging in community-driven conversations.
14 |
15 | The community is actively involved in proposing new improvements, answering questions, assisting in design decisions, and submitting pull requests. Together, we strive to enhance the capabilities and usability of JMeter DSL.
16 |
17 | ## Enterprise Support by Abstracta
18 |
19 | In addition to community support, [Abstracta](https://abstracta.us) offers enterprise-level support for JMeter DSL users. Abstracta is the main supporter of JMeter DSL development and provides specialized professional services to ensure the success of organizations using JMeter DSL. With Abstracta's enterprise support, you can accelerate your JMeter DSL implementation and have access to:
20 |
21 | * Dedicated support team : Get prompt answers and peace of mind from a dedicated support team with the expertise to help you resolve issues faster.
22 | * Customizations: Receive tailored solutions to meet your specific requirements.
23 | * Consulting services: Access a team of experts to fine-tune your JMeter DSL usage, speed up implementation, work on your performance testing strategy and overall testing processes.
24 |
25 | Abstracta is committed to helping organizations succeed with JMeter DSL by providing comprehensive support and specialized services tailored to your enterprise needs.
26 |
27 | To explore Abstracta's enterprise support options or discuss your specific needs, please [contact the Abstracta team](https://abstracta.us/contact-us).
28 |
29 | :::: grid
30 | ::: grid-logo https://abstracta.us
31 | 
32 | :::
33 | ::::
34 |
35 | ## Industry Support
36 |
37 | JMeter DSL has received valuable support from industry-leading companies, contributing to the integration features and promoting the tool. We would like to acknowledge and express our gratitude to the following companies:
38 |
39 | :::: grid
40 | ::: grid-logo https://www.blazemeter.com/
41 | 
42 | :::
43 | ::: grid-logo https://octoperf.com/
44 | 
45 | :::
46 | ::: grid-logo https://azure.microsoft.com/en-us/products/load-testing/
47 | 
48 | :::
49 | ::::
50 |
51 | [Discord server]:https://discord.gg/WNSn5hqmSd
52 | [GitHub Issues]:https://github.com/abstracta/jmeter-dotnet-dsl/issues
53 | [GitHub Discussions]:https://github.com/abstracta/jmeter-dotnet-dsl/discussions
54 |
--------------------------------------------------------------------------------
/docs/guide/request-generation/timers/constant-and-random.md:
--------------------------------------------------------------------------------
1 | #### Emulate user delays between requests
2 |
3 | Sometimes, is necessary to be able to properly replicate users' behavior, and in particular the time the users take between sending one request and the following one. For example, to simulate the time it will take to complete a purchase form. JMeter (and the DSL) provide a few alternatives for this.
4 |
5 | If you just want to add 1 pause between two requests, you can use the `ThreadPause` method like in the following example:
6 |
7 | ```cs
8 | using System;
9 | using System.Net.Http.Headers;
10 | using System.Net.Mime;
11 | using static Abstracta.JmeterDsl.JmeterDsl;
12 |
13 | public class PerformanceTest
14 | {
15 | [Test]
16 | public void LoadTest()
17 | {
18 | TestPlan(
19 | ThreadGroup(2, 10,
20 | HttpSampler("http://my.service/items"),
21 | ThreadPause(TimeSpan.FromSeconds(4)),
22 | HttpSampler("http://my.service/cart/selected-items")
23 | .Post("{\"id\": 1}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
24 | )
25 | ).Run();
26 | }
27 | }
28 | ```
29 |
30 | Using `ThreadPause` is a good solution for adding individual pauses, but if you want to add pauses across several requests, or sections of test plan, then using a `ConstantTimer` or `UniformRandomTimer` is better. Here is an example that adds a delay of between 4 and 10 seconds for every request in the test plan:
31 |
32 | ```cs
33 | using System;
34 | using System.Net.Http.Headers;
35 | using System.Net.Mime;
36 | using static Abstracta.JmeterDsl.JmeterDsl;
37 |
38 | public class PerformanceTest
39 | {
40 | [Test]
41 | public void LoadTest()
42 | {
43 | TestPlan(
44 | ThreadGroup(2, 10,
45 | UniformRandomTimer(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(10)),
46 | Transaction("addItemToCart",
47 | HttpSampler("http://my.service/items"),
48 | HttpSampler("http://my.service/cart/selected-items")
49 | .Post("{\"id\": 1}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
50 | ),
51 | Transaction("checkout",
52 | HttpSampler("http://my.service/cart/chekout"),
53 | HttpSampler("http://my.service/cart/checkout/userinfo")
54 | .poPostst(
55 | "{\"Name\": Dave, \"lastname\": Tester, \"Street\": 1483 Smith Road, \"City\": Atlanta}",
56 | new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
57 | )
58 | ).Run();
59 | }
60 | }
61 | ```
62 |
63 | ::: tip
64 | As you may have noticed, timer order in relation to samplers, doesn't matter. Timers apply to all samplers in their scope, adding a pause after pre-processor executions and before the actual sampling.
65 | `ThreadPause` order, on the other hand, is relevant, and the pause will only execute when previous samplers in the same scope have run and before following samplers do.
66 | :::
67 |
68 | ::: warning
69 | `UniformRandomTimer` `minimum` and `maximum` parameters differ from the ones used by JMeter Uniform Random Timer element, to make it simpler for users with no JMeter background.
70 |
71 | The generated JMeter test element uses the `Constant Delay Offset` set to `minimum` value, and the `Maximum random delay` set to `(maximum - minimum)` value.
72 | :::
73 |
--------------------------------------------------------------------------------
/docs/guide/scale/azure.md:
--------------------------------------------------------------------------------
1 | ### Azure Load Testing
2 |
3 | To use [Azure Load Testing](https://azure.microsoft.com/en-us/products/load-testing/) to execute your test plans at scale is as easy as including the following package to your project:
4 |
5 | ```powershell
6 | dotnet add package Abstracta.JmeterDsl.Azure --version 0.8
7 | ```
8 |
9 | And using the provided engine like this:
10 |
11 | ```cs
12 | using Abstracta.JmeterDsl.Azure;
13 | using static Abstracta.JmeterDsl.JmeterDsl;
14 |
15 | public class PerformanceTest
16 | {
17 | [Test]
18 | public void LoadTest()
19 | {
20 | var stats = TestPlan(
21 | ThreadGroup(2, 10,
22 | HttpSampler("http://my.service")
23 | )
24 | ).RunIn(new AzureEngine(Environment.GetEnvironmentVariable("AZURE_CREDS")) // AZURE_CREDS=tenantId:clientId:secretId
25 | .TestName("dsl-test")
26 | /*
27 | This specifies the number of engine instances used to execute the test plan.
28 | In this case, means that it will run 2(threads in thread group)x2(engines)=4 concurrent users/threads in total.
29 | Each engine executes the test plan independently.
30 | */
31 | .Engines(2)
32 | .TestTimeout(TimeSpan.FromMinutes(20)));
33 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
34 | }
35 | }
36 | ```
37 | > This test is using `AZURE_CREDS`, a custom environment variable containing `tenantId:clientId:clientSecret` with proper values for each. Check at [Azure Portal tenant properties](https://portal.azure.com/#view/Microsoft_AAD_IAM/TenantPropertiesBlade) the proper tenant ID for your subscription, and follow [this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) to register an application with proper permissions and secrets generation for tests execution.
38 |
39 | With Azure, you can not only run the test at scale but also get additional features like nice real-time reporting, historic data tracking, etc. Here is an example of how a test looks like in Azure Load Testing:
40 |
41 | 
42 |
43 | Check [AzureEngine](/Abstracta.JmeterDsl.Azure/AzureEngine.cs) for details on usage and available settings when running tests in Azure Load Testing.
44 |
45 | ::: warning
46 | By default, the engine is configured to time out if test execution takes more than 1 hour.
47 | This timeout exists to avoid any potential problem with Azure Load Testing execution not detected by the
48 | client, and avoid keeping the test indefinitely running until is interrupted by a user,
49 | which may incur unnecessary expenses in Azure and is especially annoying when running tests
50 | in an automated fashion, for example in CI/CD.
51 | It is strongly advised to **set this timeout properly in each run**, according to the expected test
52 | execution time plus some additional margin (to consider for additional delays in Azure Load Testing
53 | test setup and teardown) to avoid unexpected test plan execution failure (due to timeout) or
54 | unnecessary waits when there is some unexpected issue with Azure Load Testing execution.
55 | :::
56 |
57 | ::: tip
58 | If you want to get debug logs for HTTP calls to Azure API, you can include the following setting to an existing `log4j2.xml` configuration file:
59 | ```xml
60 |
61 |
62 | ```
63 | :::
64 |
--------------------------------------------------------------------------------
/Abstracta.JmeterDsl/Core/Controllers/DslTransactionController.cs:
--------------------------------------------------------------------------------
1 | using Abstracta.JmeterDsl.Core.Bridge;
2 | using Abstracta.JmeterDsl.Core.ThreadGroups;
3 |
4 | namespace Abstracta.JmeterDsl.Core.Controllers
5 | {
6 | ///
7 | /// Allows specifying JMeter transaction controllers which group different samples associated to same
8 | /// transaction.
9 | ///
10 | /// This is usually used when grouping different steps of a flow, for example group requests of login
11 | /// flow, adding item to cart, purchase, etc. It provides aggregate metrics of all it's samples.
12 | ///
13 | [YamlType(TagName = "transaction")]
14 | public class DslTransactionController : BaseController
15 | {
16 | private bool _includeTimersAndProcessorsTime;
17 | private bool _generateParentSample;
18 |
19 | public DslTransactionController(string name, IThreadGroupChild[] children)
20 | : base(name, children)
21 | {
22 | }
23 |
24 | ///
25 | /// Specifies to include time spent in timers and pre- and post-processors in sample results.
26 | ///
27 | /// the controller for further configuration or usage.
28 | public DslTransactionController IncludeTimersAndProcessorsTime() =>
29 | IncludeTimersAndProcessorsTime(true);
30 |
31 | ///
32 | /// Same as but allowing to enable or disable it.
33 | ///
34 | /// This is helpful when the resolution is taken at runtime.
35 | ///
36 | /// specifies to enable or disable the setting. By default, it is set to false.
37 | /// the controller for further configuration or usage.
38 | ///
39 | public DslTransactionController IncludeTimersAndProcessorsTime(bool enable)
40 | {
41 | _includeTimersAndProcessorsTime = enable;
42 | return this;
43 | }
44 |
45 | ///
46 | /// Specifies to create a sample result as parent of children samplers.
47 | ///
48 | /// It is useful in some scenarios to get transaction sample results as parent of children samplers
49 | /// to focus mainly in transactions and not be concerned about each particular request. Enabling
50 | /// parent sampler helps in this regard, only reporting the transactions in summary reports, and
51 | /// not the transaction children results.
52 | ///
53 | /// the controller for further configuration or usage.
54 | public DslTransactionController GenerateParentSample() =>
55 | GenerateParentSample(true);
56 |
57 | ///
58 | /// Same as but allowing to enable or disable it.
59 | ///
60 | /// This is helpful when the resolution is taken at runtime.
61 | ///
62 | /// specifies to enable or disable the setting. By default, it is set to false.
63 | /// the controller for further configuration or usage.
64 | ///
65 | public DslTransactionController GenerateParentSample(bool enable)
66 | {
67 | _generateParentSample = enable;
68 | return this;
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/docs/guide/request-generation/csv-dataset.md:
--------------------------------------------------------------------------------
1 | ### CSV as input data for requests
2 |
3 | Sometimes is necessary to run the same flow but using different pre-defined data on each request. For example, a common use case is to use a different user (from a given set) in each request.
4 |
5 | This can be easily achieved using the provided `CsvDataSet` element. For example, having a file like this one:
6 |
7 | ```csv
8 | USER,PASS
9 | user1,pass1
10 | user2,pass2
11 | ```
12 |
13 | You can implement a test plan that tests recurrent login with the two users with something like this:
14 |
15 | ```cs
16 | using System;
17 | using System.Net.Http;
18 | using System.Net.Http.Headers;
19 | using System.Net.Mime;
20 | using static Abstracta.JmeterDsl.JmeterDsl;
21 |
22 | public class PerformanceTest
23 | {
24 | [Test]
25 | public void LoadTest()
26 | {
27 | var stats = TestPlan(
28 | CsvDataSet("users.csv"),
29 | ThreadGroup(5, 10,
30 | HttpSampler("http://my.service/login")
31 | .Post("{\"${USER}\": \"${PASS}\"", new MediaTypeHeaderValue(MediaTypeNames.Application.Json)),
32 | HttpSampler("http://my.service/logout")
33 | .Method(HttpMethod.Post.Method)
34 | )
35 | ).Run();
36 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
37 | }
38 | }
39 | ```
40 |
41 | ::: tip
42 | To properly format the data in your CSV, a general rule you can apply is to replace each double quotes with two double quotes and add double quotes to the beginning and end of each CSV value.
43 |
44 | E.g.: if you want one CSV field to contain the value `{"field": "value"}`, then use `"{""field:"": ""value""}"`.
45 |
46 | This way, with a simple search and replace, you can include in a CSV field any format like JSON, XML, etc.
47 |
48 | Note: JMeter uses should be aware that JMeter DSL `CsvDataSet` sets `Allowed quoted data?` flag, in associated `Csv Data Set Config` element, to `true`.
49 | :::
50 |
51 | ::: tip
52 | By default, the CSV file will be opened once and shared by all threads. This means that when one thread reads a CSV line in one iteration, then the following thread reading a line will continue with the following line.
53 |
54 | If you want to change this (to share the file per thread group or use one file per thread), then you can use the provided `SharedIn` method like in the following example:
55 |
56 | ```java
57 | using static Abstracta.JmeterDsl.Core.Configs.DslCsvDataSet;
58 | ...
59 | var stats = TestPlan(
60 | CsvDataSet("users.csv")
61 | .SharedIn(Sharing.Thread),
62 | ThreadGroup(5, 10,
63 | HttpSampler("http://my.service/login")
64 | .Post("{\"${USER}\": \"${PASS}\"", new MediaTypeHeaderValue(MediaTypeNames.Application.Json)),
65 | HttpSampler("http://my.service/logout")
66 | .Method(HttpMethod.Post.Method)
67 | )
68 | ).Run();
69 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
70 | ```
71 | :::
72 |
73 | ::: warning
74 | You can use the `RandomOrder()` method to get CSV lines in random order (using [Random CSV Data Set plugin](https://github.com/Blazemeter/jmeter-bzm-plugins/blob/master/random-csv-data-set/RandomCSVDataSetConfig.md)), but this is less performant as getting them sequentially, so use it sparingly.
75 | :::
76 |
77 | Check [DslCsvDataSet](/Abstracta.JmeterDsl/Core/Configs/DslCsvDataSet.cs) for additional details and options (like changing delimiter, handling files without headers line, stopping on the end of file, etc.).
78 |
--------------------------------------------------------------------------------
/docs/guide/simple-test-plan.md:
--------------------------------------------------------------------------------
1 | ## Simple HTTP test plan
2 |
3 | To generate HTTP requests just use provided `HttpSampler`.
4 |
5 | The following example uses 2 threads (concurrent users) that send 10 HTTP GET requests each to `http://my.service`.
6 |
7 | Additionally, it logs collected statistics (response times, status codes, etc.) to a file (for later analysis if needed) and checks that the response time 99 percentile is less than 5 seconds.
8 |
9 | ```cs
10 | using static Abstracta.JmeterDsl.JmeterDsl;
11 |
12 | public class PerformanceTest
13 | {
14 | [Test]
15 | public void LoadTest()
16 | {
17 | var stats = TestPlan(
18 | ThreadGroup(2, 10,
19 | HttpSampler("http://my.service")
20 | ),
21 | //this is just to log details of each request stats
22 | JtlWriter("jtls")
23 | ).Run();
24 | Assert.That(stats.Overall.SampleTimePercentile99, Is.LessThan(TimeSpan.FromSeconds(5)));
25 | }
26 | }
27 | ```
28 |
29 | ::: tip
30 | When working with multiple samplers in a test plan, specify their names (eg: `HttpSampler("home", "http://my.service")`) to easily check their respective statistics.
31 | :::
32 |
33 | ::: tip
34 | JMeter .Net DSL uses Java for executing JMeter test plans. If you need to tune JVM parameters, for example for specifying maximum heap memory size, you can use `EmbeddedJMeterEngine` and the `JvmArgs` method like in the following example:
35 |
36 | ```cs
37 | using Abstracta.JmeterDsl.Core.Engines;
38 | ...
39 | var stats = TestPlan(
40 | ThreadGroup(2, 10,
41 | HttpSampler("http://my.service")
42 | )
43 | ).RunIn(new EmbeddedJmeterEngine()
44 | .JvmArgs("-Xmx4g")
45 | );
46 | ```
47 | :::
48 |
49 | ::: tip
50 | Since JMeter uses [log4j2](https://logging.apache.org/log4j/2.x/), if you want to control the logging level or output, you can use something similar to this [log4j2.xml](Abstracta.JmeterDsl.Tests/log4j2.xml), using "CopyToOutputDirectory" in the project item, so the file is available in dotnet build output directory as well (check [Abstracta.JmeterDsl.Test/Abstracta.JmeterDsl.Tests.csproj]).
51 | :::
52 |
53 | ::: tip
54 | Depending on the test framework you use, and the way you run your tests, you might be able to see JMeter logs and output in real-time, at the end of the test, or not see them at all. This is not something we can directly control in JMeter DSL, and heavily depends on the dotnet environment and testing framework implementation.
55 |
56 | When using Nunit, to get real-time console output from JMeter you might want to run your tests with something like `dotnet test -v n` and add the following code to your tests:
57 |
58 | ```cs
59 | private TextWriter? originalConsoleOut;
60 |
61 | // Redirecting output to progress to get live stdout with nunit.
62 | // https://github.com/nunit/nunit3-vs-adapter/issues/343
63 | // https://github.com/nunit/nunit/issues/1139
64 | [SetUp]
65 | public void SetUp()
66 | {
67 | originalConsoleOut = Console.Out;
68 | Console.SetOut(TestContext.Progress);
69 | }
70 |
71 | [TearDown]
72 | public void TearDown()
73 | {
74 | Console.SetOut(originalConsoleOut!);
75 | }
76 | ```
77 | :::
78 |
79 | ::: tip
80 | Keep in mind that you can use .Net programming to modularize and create abstractions which allow you to build complex test plans that are still easy to read, use and maintain. [Here is an example](https://github.com/abstracta/jmeter-java-dsl/issues/26#issuecomment-953783407) of some complex abstraction built using Java features (you can easily extrapolate to .Net) and the DSL.
81 | :::
82 |
83 | Check [HTTP performance testing](./protocols/http/index#http) for additional details while testing HTTP services.
--------------------------------------------------------------------------------
/docs/guide/request-generation/group-requests.md:
--------------------------------------------------------------------------------
1 | ### Group requests
2 |
3 | Sometimes, is necessary to be able to group requests which constitute different steps in a test. For example, to separate necessary requests to do a login from the ones used to add items to the cart and the ones to do a purchase. JMeter (and the DSL) provide Transaction Controllers for this purpose, here is an example:
4 |
5 | ```cs
6 | using System.Net.Http.Headers;
7 | using System.Net.Mime;
8 | using static Abstracta.JmeterDsl.JmeterDsl;
9 |
10 | public class PerformanceTest
11 | {
12 | [Test]
13 | public void TestTransactions()
14 | {
15 | TestPlan(
16 | ThreadGroup(2, 10,
17 | Transaction("login",
18 | HttpSampler("http://my.service"),
19 | HttpSampler("http://my.service/login")
20 | .Post("user=test&password=test", new MediaTypeHeaderValue(MediaTypeNames.Application.FormUrlEncoded))
21 | ),
22 | Transaction("checkout",
23 | HttpSampler("http://my.service/items"),
24 | HttpSampler("http://my.service/cart/items")
25 | .poPostst("{\"id\": 1}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
26 | )
27 | ).Run();
28 | }
29 | }
30 | ```
31 |
32 | This will provide additional sample results for each transaction, which contain the aggregate metrics for containing requests, allowing you to focus on the actual flow steps instead of each particular request.
33 |
34 | If you don't want to generate additional sample results (and statistics), and want to group requests for example to apply a given timer, config, assertion, listener, pre- or post-processor, then you can use `SimpleController` like in following example:
35 |
36 | ```cs
37 | using static Abstracta.JmeterDsl.JmeterDsl;
38 |
39 | public class PerformanceTest
40 | {
41 | [Test]
42 | public void TestTransactions()
43 | {
44 | TestPlan(
45 | ThreadGroup(2, 10,
46 | simpleController("login",
47 | HttpSampler("http://my.service"),
48 | HttpSampler("http://my.service/users")
49 | ResponseAssertion()
50 | .ContainsSubstrings("OK")
51 | )
52 | )
53 | ).Run();
54 | }
55 | }
56 | ```
57 |
58 | You can even use `TransactionController` and `SimpleController` to easily modularize parts of your test plan into Java methods (or classes) like in this example:
59 |
60 | ```cs
61 | using System.Net.Http.Headers;
62 | using System.Net.Mime;
63 | using static Abstracta.JmeterDsl.JmeterDsl;
64 |
65 | public class PerformanceTest
66 | {
67 | private DslTransactionController Login(string baseUrl) =>
68 | Transaction("login",
69 | HttpSampler(baseUrl),
70 | HttpSampler(baseUrl + "/login")
71 | .Post("user=test&password=test", new MediaTypeHeaderValue(MediaTypeNames.Application.FormUrlEncoded))
72 | );
73 |
74 | private DslTransactionController AddItemToCart(string baseUrl) =>
75 | Transaction("addItemToCart",
76 | HttpSampler(baseUrl + "/items"),
77 | HttpSampler(baseUrl + "/cart/items")
78 | .Post("{\"id\": 1}", new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
79 | );
80 |
81 | [Test]
82 | public void TestTransactions()
83 | {
84 | var baseUrl = "http://my.service";
85 | TestPlan(
86 | ThreadGroup(2, 10,
87 | Login(baseUrl),
88 | AddItemToCart(baseUrl)
89 | )
90 | ).Run();
91 | }
92 | }
93 | ```
94 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | run-name: release ${{ inputs.version }}
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | required: true
8 | type: string
9 | jobs:
10 | release:
11 | runs-on: ubuntu-latest
12 | concurrency: remote_test
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Setup .NET Core
16 | uses: actions/setup-dotnet@v4
17 | with:
18 | dotnet-version: 6
19 | - uses: actions/setup-java@v4
20 | with:
21 | distribution: temurin
22 | java-version: 11
23 | cache: maven
24 | - uses: actions/setup-node@v4
25 | with:
26 | node-version: '20'
27 | - uses: pnpm/action-setup@v3
28 | with:
29 | version: 8
30 | - name: check version
31 | run: .github/semver-check.sh ${{ inputs.version }}
32 | - name: create release draft
33 | uses: ncipollo/release-action@v1
34 | with:
35 | tag: v${{ inputs.version }}
36 | name: ${{ inputs.version }}
37 | draft: true
38 | - name: set project version
39 | run: .github/fix-project-version.sh ${{ inputs.version }}
40 | - name: update docs version
41 | run: .github/fix-docs-version.sh ${{ inputs.version }}
42 | - name: commit release version
43 | uses: stefanzweifel/git-auto-commit-action@v5
44 | with:
45 | commit_message: '[skip ci] Set release version'
46 | branch: main
47 | file_pattern: '*/*.csproj README.md docs/index.md docs/guide/**'
48 | - name: build
49 | run: dotnet build --configuration Release
50 | - name: test
51 | # avoid running tests in parallel since it may happen that two tests try to modify .jmeter-dsl directory
52 | run: dotnet test -m:1 --no-build --configuration Release --verbosity normal
53 | env:
54 | AZURE_CREDS: ${{ secrets.AZURE_CREDS }}
55 | BZ_TOKEN: ${{ secrets.BZ_TOKEN }}
56 | - name: package
57 | run: dotnet pack --no-build --configuration Release
58 | - name: publish to Nuget
59 | run: .github/nuget-deploy.sh
60 | env:
61 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
62 | - name: publish GH release
63 | uses: ncipollo/release-action@v1
64 | with:
65 | tag: v${{ inputs.version }}
66 | allowUpdates: true
67 | omitNameDuringUpdate: true
68 | omitBodyDuringUpdate: true
69 | updateOnlyUnreleased: true
70 | draft: false
71 | - name: get next alpha version
72 | run: echo "ALPHA_VERSION=$(.github/next-minor-alpha.sh ${{ inputs.version }})" >> $GITHUB_ENV
73 | - name: update to next ALPHA version
74 | run: .github/fix-project-version.sh ${{ env.ALPHA_VERSION }}
75 | - name: commit ALPHA version
76 | uses: stefanzweifel/git-auto-commit-action@v5
77 | with:
78 | commit_message: '[skip ci] Set ALPHA version'
79 | branch: main
80 | file_pattern: '*/*.csproj README.md docs/index.md docs/guide/**'
81 | - name: deploy github pages
82 | run: .github/vuepress-deploy.sh
83 | env:
84 | ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
85 | - uses: actions/checkout@v4
86 | with:
87 | repository: abstracta/jmeter-dotnet-dsl-sample
88 | path: jmeter-dotnet-dsl-sample
89 | token: ${{ secrets.ACTIONS_TOKEN }}
90 | - name: update version in sample project
91 | run: .github/update-sample-version.sh ${{ inputs.version }}
92 | - name: Discord notification
93 | env:
94 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
95 | uses: Ilshidur/action-discord@master
96 | with:
97 | args: 'A new release is out! Check it at https://github.com/abstracta/jmeter-dotnet-dsl/releases/tag/v${{ inputs.version }}'
98 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.ts:
--------------------------------------------------------------------------------
1 | import { defineUserConfig } from '@vuepress/cli'
2 | import { defaultTheme } from '@vuepress/theme-default'
3 | import { getDirname, path, fs } from '@vuepress/utils'
4 | import { searchPlugin } from '@vuepress/plugin-search'
5 | import { mediumZoomPlugin } from '@vuepress/plugin-medium-zoom'
6 | import { containerPlugin } from '@vuepress/plugin-container'
7 | import { mdEnhancePlugin } from "vuepress-plugin-md-enhance"
8 | import { repoLinkSolverPlugin } from "./plugins/repoLinkSolverPlugin"
9 | import { includedRelativeLinkSolverPlugin } from "./plugins/includedRelativeLinkSolverPlugin"
10 | import { copyCodePlugin } from "vuepress-plugin-copy-code2"
11 |
12 | const __dirname = getDirname(import.meta.url)
13 |
14 | const REPO_LINK = "https://github.com/abstracta/jmeter-dotnet-dsl"
15 |
16 |
17 | export default defineUserConfig({
18 | lang: 'en-US',
19 | title: 'jmeter-dotnet-dsl',
20 | description: 'Simple JMeter performance tests API',
21 | base: '/jmeter-dotnet-dsl/',
22 | head: [
23 | ['link', { rel: 'shortcut icon', href: '/jmeter-dotnet-dsl/favicon.ico'}],
24 | // when changing this remember also changing components/NavbarBrand.vue
25 | ['script', {}, `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
26 | new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
27 | j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
28 | 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
29 | })(window,document,'script','dataLayer','GTM-PHSGKLD');
30 | `]
31 | ],
32 | // restrict pattern to avoid going into included pages
33 | pagePatterns: ["*.md", "*/index.md", "!.vuepress", "!node_modules"],
34 | markdown: {
35 | headers: {
36 | level: [2, 3, 4]
37 | }
38 | },
39 | theme: defaultTheme({
40 | logo: '/logo.svg',
41 | editLink: false,
42 | lastUpdated: false,
43 | contributors: false,
44 | navbar: [
45 | {
46 | text: 'Guide',
47 | link: '/guide/',
48 | },
49 | {
50 | text: 'Support',
51 | link: '/support/',
52 | },
53 | {
54 | text: 'Motivation',
55 | link: 'https://abstracta.github.io/jmeter-java-dsl/motivation/',
56 | },
57 | {
58 | link: "https://discord.gg/WNSn5hqmSd",
59 | icon: ['fab', 'discord']
60 | },
61 | {
62 | link: REPO_LINK,
63 | icon: ['fab', 'github']
64 | }
65 | ],
66 | sidebarDepth: 3
67 | }),
68 | alias: {
69 | '@theme/NavbarBrand.vue': path.resolve(__dirname, './components/NavbarBrand.vue'),
70 | '@theme/AutoLink.vue': path.resolve(__dirname, './components/AutoLink.vue'),
71 | '@theme/HomeHero.vue': path.resolve(__dirname, './components/HomeHero.vue'),
72 | '@theme/HomeFeatures.vue': path.resolve(__dirname, './components/HomeFeatures.vue'),
73 | },
74 | plugins: [
75 | searchPlugin({ maxSuggestions: 10 }),
76 | mdEnhancePlugin({
77 | include: {
78 | deep: true,
79 | resolveImagePath: true,
80 | resolveLinkPath: true,
81 | resolvePath: (filePath: string, cwd: string | null) => {
82 | let ret = path.join(cwd, filePath)
83 | if (!fs.existsSync(ret)) {
84 | throw new Error(`File ${ret} not found.`)
85 | }
86 | return ret;
87 | }
88 | }
89 | }),
90 | repoLinkSolverPlugin({ repoUrl: REPO_LINK }),
91 | includedRelativeLinkSolverPlugin({}),
92 | copyCodePlugin({ pure: true }),
93 | containerPlugin({
94 | type: 'grid',
95 | before: (info: string): string => `