Skip to content

Commit 96826e7

Browse files
Allow registering thread and iteration listeners to jsr223TestElements as to ease integration with seleniumScripts
1 parent 777dee8 commit 96826e7

File tree

5 files changed

+210
-11
lines changed

5 files changed

+210
-11
lines changed

docs/guide/protocols/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
<!-- @include: graphql.md -->
55
<!-- @include: jdbc.md -->
66
<!-- @include: java.md -->
7+
<!-- @include: selenium.md -->

docs/guide/protocols/java.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,56 @@ jsr223Sampler(v -> {
7373
As previously mentioned, even though using Java Lambdas has several benefits, they are also less portable. Check [this section](../response-processing/jsr223-post-processor.md#lambdas) for more details.
7474
:::
7575

76-
`jsr223Sampler` is very powerful, but also makes code and test plans harder to maintain (as with any custom code) compared to using JMeter built-in samplers. So, in general, prefer using JMeter-provided samplers if they are enough for the task at hand, and use `jsr223Sampler` sparingly.
76+
You may even use some custom logic that executes a particular logic when a thread group thread is created and finished. Here is an example:
77+
78+
```java
79+
public class TestRedis {
80+
81+
public static class RedisSampler implements SamplerScript, ThreadListener {
82+
83+
private Jedis jedis;
84+
85+
@Override
86+
public void threadStarted() {
87+
jedis = new Jedis("localhost", 6379);
88+
jedis.connect();
89+
}
90+
91+
@Override
92+
public void runScript(SamplerVars v) {
93+
jedis.set("foo", "bar");
94+
v.sampleResult.setResponseData(jedis.get("foo"), StandardCharsets.UTF_8.name());
95+
}
96+
97+
@Override
98+
public void threadFinished() {
99+
jedis.close();
100+
}
101+
102+
}
103+
104+
@Test
105+
public void shouldGetExpectedSampleResultWhenJsr223SamplerWithLambdaAndCustomResponse()
106+
throws IOException {
107+
TestPlanStats stats = testPlan(
108+
threadGroup(2, 10,
109+
jsr223Sampler(RedisSampler.class)
110+
)
111+
).run();
112+
assertThat(stats.overall().sampleTimePercentile99()).isLessThan(Duration.ofMillis(500));
113+
}
114+
115+
}
116+
```
117+
118+
::: tip
119+
You can also make your class implement `TestIterationListener` to execute custom logic on each thread group iteration start, or `LoopIterationListener` to execute some custom logic on each iteration start (for example, each iteration of a `forLoop`).
120+
:::
121+
122+
::: tip
123+
When using public static classes in `jsr223Sampler` take into consideration that one instance of the class is created for each thread group thread and `jsr223Sampler` instance.
124+
:::
125+
126+
**Note:** `jsr223Sampler` is very powerful, but also makes code and test plans harder to maintain (as with any custom code) compared to using JMeter built-in samplers. So, in general, prefer using JMeter-provided samplers if they are enough for the task at hand, and use `jsr223Sampler` sparingly.
77127

78128
Check [DslJsr223Sampler](/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/java/DslJsr223Sampler.java) for more details and additional options.

docs/guide/protocols/selenium.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
### Selenium
2+
3+
With JMeter DSL is quite simple to integrate your existing selenium scripts into performance tests. One common use case is to do real user monitoring or synthetics monitoring (get time spent in particular parts of a Selenium script) while the backend load is being generated.
4+
5+
Here is an example of how you can do this with JMeter DSL:
6+
7+
```java
8+
public class PerformanceTest {
9+
10+
public static class SeleniumSampler implements SamplerScript, ThreadListener {
11+
12+
private WebDriver driver;
13+
14+
@Override
15+
public void threadStarted() {
16+
driver = new ChromeDriver(); // you can invoke existing set up logic to reuse it
17+
}
18+
19+
@Override
20+
public void runScript(SamplerVars v) {
21+
driver.get("https://mysite"); // you can invoke existing selenium script for reuse here
22+
}
23+
24+
@Override
25+
public void threadFinished() {
26+
driver.close(); // you can invoke existing tear down logic to reuse it
27+
}
28+
29+
}
30+
31+
@Test
32+
public void shouldGetExpectedSampleResultWhenJsr223SamplerWithLambdaAndCustomResponse()
33+
throws IOException {
34+
Duration testPlanDuration = Duration.ofMinutes(10);
35+
TestPlanStats stats = testPlan(
36+
threadGroup(1, testPlanDuration,
37+
jsr223Sampler("Real User Monitor", SeleniumSampler.class)
38+
),
39+
threadGroup(100, testPlanDuration,
40+
httpSampler("https://mysite/products")
41+
.post("{\"name\": \"test\"}", Type.APPLICATION_JSON)
42+
)
43+
).run();
44+
assertThat(stats.overall().sampleTimePercentile99()).isLessThan(Duration.ofMillis(500));
45+
}
46+
47+
}
48+
```
49+
50+
Check [previous section](./java.md#java-api-performance-testing) for more details on `jsr223Sampler`.

jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/testelements/DslJsr223TestElement.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import java.lang.reflect.Method;
44
import java.util.List;
5+
import org.apache.jmeter.engine.event.LoopIterationEvent;
6+
import org.apache.jmeter.engine.event.LoopIterationListener;
57
import org.apache.jmeter.testbeans.TestBean;
68
import org.apache.jmeter.testbeans.gui.TestBeanGUI;
79
import org.apache.jmeter.testelement.AbstractTestElement;
810
import org.apache.jmeter.testelement.TestElement;
11+
import org.apache.jmeter.testelement.TestIterationListener;
12+
import org.apache.jmeter.testelement.ThreadListener;
913
import org.apache.jmeter.threads.JMeterContext;
1014
import org.apache.jmeter.util.JMeterUtils;
1115
import org.apache.jmeter.util.JSR223TestElement;
@@ -79,7 +83,8 @@ protected TestElement buildTestElement() {
7983
protected abstract Jsr223DslLambdaTestElement<V> buildLambdaTestElement();
8084

8185
public abstract static class Jsr223DslLambdaTestElement<V extends Jsr223ScriptVars> extends
82-
AbstractTestElement implements TestBean {
86+
AbstractTestElement implements TestBean, ThreadListener, TestIterationListener,
87+
LoopIterationListener {
8388

8489
private static final String SCRIPT_ID_PROP = "SCRIPT_ID";
8590
private Jsr223Script<V> script;
@@ -97,19 +102,47 @@ public String getScriptId() {
97102
return getPropertyAsString(SCRIPT_ID_PROP);
98103
}
99104

105+
@Override
106+
public void threadStarted() {
107+
script = getScript();
108+
if (script instanceof ThreadListener) {
109+
((ThreadListener) script).threadStarted();
110+
}
111+
}
112+
113+
private Jsr223Script<V> getScript() {
114+
String scriptId = getScriptId();
115+
Jsr223Script<V> script = DslScriptRegistry.findLambdaScript(scriptId);
116+
try {
117+
return script != null ? script : (Jsr223Script<V>) Class.forName(scriptId).newInstance();
118+
} catch (ReflectiveOperationException e) {
119+
throw new RuntimeException(e);
120+
}
121+
}
122+
123+
@Override
124+
public void testIterationStart(LoopIterationEvent event) {
125+
if (script instanceof TestIterationListener) {
126+
((TestIterationListener) script).testIterationStart(event);
127+
}
128+
}
129+
130+
@Override
131+
public void iterationStart(LoopIterationEvent iterEvent) {
132+
if (script instanceof LoopIterationListener) {
133+
((LoopIterationListener) script).iterationStart(iterEvent);
134+
}
135+
}
136+
100137
public void run(V vars) throws Exception {
101-
getScript().run(vars);
138+
script.run(vars);
102139
}
103140

104-
private Jsr223Script<V> getScript() throws ReflectiveOperationException {
105-
if (script == null) {
106-
String scriptId = getScriptId();
107-
script = DslScriptRegistry.findLambdaScript(scriptId);
108-
if (script == null) {
109-
script = (Jsr223Script<V>) Class.forName(scriptId).newInstance();
110-
}
141+
@Override
142+
public void threadFinished() {
143+
if (script instanceof ThreadListener) {
144+
((ThreadListener) script).threadFinished();
111145
}
112-
return script;
113146
}
114147

115148
}

jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/java/DslJsr223SamplerTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package us.abstracta.jmeter.javadsl.java;
22

33
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
4+
import static us.abstracta.jmeter.javadsl.JmeterDsl.forLoopController;
45
import static us.abstracta.jmeter.javadsl.JmeterDsl.jsr223Sampler;
56
import static us.abstracta.jmeter.javadsl.JmeterDsl.jtlWriter;
67
import static us.abstracta.jmeter.javadsl.JmeterDsl.testPlan;
@@ -9,13 +10,22 @@
910

1011
import java.nio.charset.StandardCharsets;
1112
import java.nio.file.Path;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
import org.apache.jmeter.engine.event.LoopIterationEvent;
17+
import org.apache.jmeter.engine.event.LoopIterationListener;
1218
import org.apache.jmeter.samplers.SampleResult;
19+
import org.apache.jmeter.testelement.TestIterationListener;
20+
import org.apache.jmeter.testelement.ThreadListener;
1321
import org.junit.jupiter.api.Nested;
1422
import org.junit.jupiter.api.Test;
1523
import org.junit.jupiter.api.io.TempDir;
1624
import us.abstracta.jmeter.javadsl.codegeneration.MethodCallBuilderTest;
1725
import us.abstracta.jmeter.javadsl.core.DslTestPlan;
1826
import us.abstracta.jmeter.javadsl.core.listeners.JtlWriter;
27+
import us.abstracta.jmeter.javadsl.java.DslJsr223Sampler.SamplerScript;
28+
import us.abstracta.jmeter.javadsl.java.DslJsr223Sampler.SamplerVars;
1929

2030
public class DslJsr223SamplerTest {
2131

@@ -66,6 +76,61 @@ public void shouldGetExpectedSampleResultWhenJsr223SamplerWithLambdaAndCustomRes
6676
assertThatJtlContentIsExpectedForCustomSample(resultsFilePath);
6777
}
6878

79+
public static class MySampler implements SamplerScript, ThreadListener, TestIterationListener,
80+
LoopIterationListener {
81+
82+
private static final Map<String, Integer> COUNTS = new ConcurrentHashMap<>();
83+
private int count;
84+
85+
@Override
86+
public void threadStarted() {
87+
count = 1;
88+
}
89+
90+
@Override
91+
public void testIterationStart(LoopIterationEvent event) {
92+
count++;
93+
}
94+
95+
@Override
96+
public void iterationStart(LoopIterationEvent iterEvent) {
97+
count++;
98+
}
99+
100+
@Override
101+
public void runScript(SamplerVars vars) {
102+
count++;
103+
}
104+
105+
@Override
106+
public void threadFinished() {
107+
COUNTS.put(Thread.currentThread().getName(), count);
108+
}
109+
110+
}
111+
112+
@Test
113+
public void shouldGetExpectedCountsWhenSamplerWithListeners() throws Exception {
114+
int threadCount = 2;
115+
int iterations = 3;
116+
int loops = 3;
117+
testPlan(threadGroup(threadCount, iterations,
118+
forLoopController(loops,
119+
jsr223Sampler(MySampler.class))))
120+
.run();
121+
assertThat(MySampler.COUNTS).isEqualTo(
122+
buildExpectedThreadCountsMap(threadCount, iterations, loops));
123+
}
124+
125+
private Map<String, Integer> buildExpectedThreadCountsMap(int threadCount, int iterations,
126+
int loops) {
127+
Map<String, Integer> ret = new HashMap<>();
128+
for (int i = 0; i < threadCount; i++) {
129+
ret.put("Thread Group 1-" + (i + 1), 1 + iterations + iterations * loops * 2);
130+
}
131+
return ret;
132+
}
133+
69134
@Nested
70135
public class CodeBuilderTest extends MethodCallBuilderTest {
71136

0 commit comments

Comments
 (0)