├── .github
└── no-response.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── docs
├── after.png
├── before.png
└── config.png
├── pom.xml
└── src
├── main
├── java
│ └── nz
│ │ └── co
│ │ └── breakpoint
│ │ └── jmeter
│ │ └── modifiers
│ │ ├── HTTPFormManager.java
│ │ └── HTTPFormManagerBeanInfo.java
└── resources
│ └── nz
│ └── co
│ └── breakpoint
│ └── jmeter
│ └── modifiers
│ └── HTTPFormManagerResources.properties
└── test
└── java
└── nz
└── co
└── breakpoint
└── jmeter
└── modifiers
└── TestHTTPFormManager.java
/.github/no-response.yml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | deploy:
3 | provider: releases
4 | api_key:
5 | secure: H5NtYfXctlGYNPe8SqEdGver9trS8L4OC4Cu2YkKMMw2giCQ92HYIfw9EeLwvzvuvVroG//c7ht2hwviYypTsXS0UTHfGY/q1JsMdpVc+cEl6hI8blSwH38dyTTXkb9FeIjOAycb+4UNqr/MwT/me+IKkBQQrgAe2AkstcxtLAzaDl1M4lVZu7r40+voXaczcBxUX5gUUOEziY7KAsCcNnfgsuqSIkJjxyRl2Stpi1RwVhkPWqV8TevY8XK9p4kXFd+LdShqwjLq7EcdOqFw425xxRhNgXtAETfMPHH04NcvtkXUWOhv4O1GQRhBT5yTksJgBxN6HiIif0UM3+PhEQooX/GOTqJ96V5buLWj8JO+1Nchar68GpY5209E5PbJcepywdIuRKSlCDKZ6h+44AHT+Jse/j7xxckPdNnUwSvMy9MWij+x4EDXPY0RN3VZGZitP+9oyVaK+ygrYLmKt60X4cwR4I0ZhPvthToVSsOGf8x8QYrMRQFFSwen1kmRhZ259wya3XxcAia6lbgZ2Fg9hoEcTEviOw113rWG/psw98CflJEc0wdenmc+IXilKNB/xVxK2hGbayVbKtWJmhJNoYjkpSiouiF7DD/ub0MUC2iZqQezKueLhGjSGHSoMaBufkEGiiKvXCMXFxDF126NedfAumH3e1Qe5TE5I9M=
6 | file: "target/jmeter-formman-*.jar"
7 | file_glob: true
8 | skip_cleanup: true
9 | overwrite: true
10 | on:
11 | repo: tilln/jmeter-formman
12 | tags: true
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Till Neunast
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTTP Form Manager [![travis][travis-image]][travis-url]
2 |
3 | [travis-image]: https://travis-ci.org/tilln/jmeter-formman.svg?branch=master
4 | [travis-url]: https://travis-ci.org/tilln/jmeter-formman
5 |
6 | Overview
7 | --------
8 |
9 | This plugin makes JMeter behave a little more like a browser: form fields are automatically populated with preselected values.
10 |
11 | * No more manual correlation of hidden inputs, such as session variables.
12 | * Just correlate parameters that actually relate to user input.
13 |
14 | 
15 |
16 | HTTP form parameters are extracted at runtime from HTML responses and added to HTTP sampler parameters
17 | (similar to JMeter's [HTML Link Parser](http://jmeter.apache.org/usermanual/component_reference.html#HTML_Link_Parser)).
18 |
19 | 
20 |
21 | Installation
22 | ------------
23 |
24 | ### Via [PluginsManager](https://jmeter-plugins.org/wiki/PluginsManager/)
25 |
26 | Under tab "Available Plugins", select "HTTP Form Manager", then click "Apply Changes and Restart JMeter".
27 |
28 | ### Via Package from [JMeter-Plugins.org](https://jmeter-plugins.org/)
29 |
30 | Extract the [zip package](https://jmeter-plugins.org/files/packages/tilln-formman-1.0.zip) into JMeter's lib directory, then restart JMeter.
31 |
32 |
33 | ### Via Manual Download
34 |
35 | Copy the [jmeter-formman jar file](https://github.com/tilln/jmeter-formman/releases/download/1.0/jmeter-formman-1.0.jar)
36 | into JMeter's lib/ext directory and restart JMeter.
37 |
38 | Usage
39 | -----
40 |
41 | From the context menu, select "Add" / "Pre Processors" / "HTTP Form Manager".
42 |
43 | Put the Preprocessor in a scope that contains a series of related HTTP samplers that would normally have to be chained together via Extractors.
44 |
45 | ### Simple Example
46 |
47 | Suppose an HTML response contains a login form with a few parameters, some hidden inputs, and a submit button:
48 |
49 | ```html
50 |
51 |
52 |
63 |
64 |
65 | ```
66 |
67 | Normally these form parameters would be extracted into JMeter variables via
68 | [Regular Expression](http://jmeter.apache.org/usermanual/component_reference.html#Regular_Expression_Extractor) or
69 | [CSS/JQuery Extractors](http://jmeter.apache.org/usermanual/component_reference.html#CSS/JQuery_Extractor)
70 | and then added to the subsequent HTTP sampler, as in the screenshot above.
71 |
72 | The *HTTP Form Manager* can replace those extractors and list of parameters,
73 | reducing the parameter list to just the username and password elements.
74 | It can be especially useful for parameter names that are generated dynamically by the application and change all the time.
75 |
76 | The plugin will copy all form parameters to the sampler, except the ones that are already defined in the sampler
77 | (the username and password in the screenshot).
78 |
79 | ### Multiple Forms
80 |
81 | Suppose the HTML response contains more than one form, then the plugin needs to determine which form to copy from.
82 |
83 | There are several options for identifying which form should match the current sampler.
84 |
85 | ##### Example: By sampler URL
86 |
87 | ```html
88 |
89 |
90 |
95 |
100 |
101 |
102 | ```
103 |
104 | If the HTTP Sampler's *Path* matches `/form1` and the *Method* is POST, then the parameters `__VIEWSTATE` and `submit` will be copied as follows:
105 |
106 | |Parameter |Value |Added by|
107 | |-----------|-------|--------|
108 | |__VIEWSTATE|value1 |plugin |
109 | |submit |submit1|plugin |
110 |
111 | ##### Example: By submit element
112 |
113 | Should both forms have the same URL path and method, then the value of the submit element may be used instead:
114 |
115 | ```html
116 |
117 |
118 |
124 |
130 |
131 |
132 | ```
133 |
134 | The plugin will try to match the form's submit element's name and value to a sampler parameter.
135 | So if the user added `submit1` the plugin adds `value1` for `__VIEWSTATE`:
136 |
137 | |Parameter |Value |Added by|
138 | |-----------|-------|--------|
139 | |submit |submit1|user |
140 | |__VIEWSTATE|value1 |plugin |
141 |
142 | However, if the user added `other-submit` both forms would match and therefore no modification performed.
143 |
144 | ##### Example: By sampler parameters
145 |
146 | A unique set of parameters can also be used to identify the form, for instance if the submit elements have the same value.
147 |
148 | ```html
149 |
150 |
151 |
157 |
163 |
164 |
165 | ```
166 |
167 | The plugin will try to find a form that contains all of the user-defined parameters.
168 | So if the user added `param2` the plugin would copy `__VIEWSTATE` and `submit` from the second form:
169 |
170 | |Parameter |Value |Added by|
171 | |-----------|-------|--------|
172 | |param2 |text2 |user |
173 | |__VIEWSTATE|value2 |plugin |
174 | |submit |submit |plugin |
175 |
176 | ##### Example: By CSS selector
177 |
178 | A CSS selector expression may also be used that uniquely identifies one of the forms.
179 |
180 | ```html
181 |
182 |
183 |
188 |
193 |
194 |
195 | ```
196 |
197 | The selector `[value=submit2]` would identify the second form, whereas `[name=submit]` would be ambiguous as both forms match.
198 |
199 |
200 | Configuration
201 | -------------
202 |
203 | 
204 |
205 | * Options:
206 | * "Clear form data each iteration?": Whether to discard any stored form input data when starting a new thread iteration (default: true).
207 | This avoids copying no longer relevant parameters e.g. when a new user session starts.
208 |
209 | * Form Identification:
210 | If a response contains multiple forms, the following options can be used, in any combination,
211 | to uniquely identify which form to copy parameters from:
212 | * "By sampler URL": Consider only forms with the same URL and method (POST or GET) as in the sampler.
213 | * "By sampler parameters": Consider only forms that contain all the samplers given parameters (name and value).
214 | * "By submit element": Consider only forms that contain the sampler's given submit element.
215 | * "By CSS selector": Consider only forms that contain elements selected by a CSS expression (unless empty).
216 |
217 | All of these options will be used in conjunction to narrow down a set of forms.
218 | If there is no match or more than one match, nothing will be copied, and the sampler's parameter list will not be modified at all.
219 |
220 | * Sampler Modification:
221 | * "Copy form parameters": Whether to copy form parameters from the identified form to the sampler (default: true).
222 | * "Copy form URL": Whether to copy the URL of the identified form to the sampler (default: false).
223 | Useful if the form URL is not static, e.g. contains dynamic query parameters.
224 |
225 | By default, only HTTP Sampler responses with Content-Type *text/html* will be considered.
226 | The JMeter Property `jmeter.formman.contentType` can be used to redefine that, e.g. *application/xhtml+xml*.
227 |
228 | Limitations
229 | -----------
230 |
231 | * Form parameters that are not part of the previous HTML response, or are added or modified by JavaScript, need to be correlated manually as usual.
232 |
233 | * The plugin has no effect if a form cannot be determined unambiguously.
234 |
235 | * Button attributes formaction, formmethod, form are not supported.
236 |
237 | * Usage of more than one HTTP Form Manager in a sampler's scope is untested.
--------------------------------------------------------------------------------
/docs/after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tilln/jmeter-formman/0fc505da3781ae26d427dfdb8ac8332739603b25/docs/after.png
--------------------------------------------------------------------------------
/docs/before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tilln/jmeter-formman/0fc505da3781ae26d427dfdb8ac8332739603b25/docs/before.png
--------------------------------------------------------------------------------
/docs/config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tilln/jmeter-formman/0fc505da3781ae26d427dfdb8ac8332739603b25/docs/config.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | nz.co.breakpoint
5 | jmeter-formman
6 | jar
7 | 1.0
8 | JMeter HTTP Form Manager
9 | http://maven.apache.org
10 |
11 |
12 | UTF-8
13 | 1.8
14 | 1.8
15 |
16 |
17 |
18 | https://github.com/tilln/jmeter-formman
19 | https://github.com/tilln/jmeter-formman.git
20 | scm:git:git@github.com:tilln/jmeter-formman.git
21 |
22 |
23 |
24 |
25 | org.apache.jmeter
26 | ApacheJMeter_core
27 | 3.1
28 |
29 |
30 | org.apache.jmeter
31 | ApacheJMeter_http
32 | 3.1
33 |
34 |
35 | org.jsoup
36 | jsoup
37 | [1.10.2,)
38 | provided
39 |
40 |
41 | org.hamcrest
42 | hamcrest-library
43 | 1.3
44 | test
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/java/nz/co/breakpoint/jmeter/modifiers/HTTPFormManager.java:
--------------------------------------------------------------------------------
1 | package nz.co.breakpoint.jmeter.modifiers;
2 |
3 | import java.net.MalformedURLException;
4 | import java.net.URL;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Set;
8 | import org.apache.jmeter.engine.event.LoopIterationEvent;
9 | import org.apache.jmeter.processor.PreProcessor;
10 | import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
11 | import org.apache.jmeter.samplers.Sampler;
12 | import org.apache.jmeter.samplers.SampleResult;
13 | import org.apache.jmeter.testbeans.TestBean;
14 | import org.apache.jmeter.testelement.AbstractTestElement;
15 | import org.apache.jmeter.testelement.TestIterationListener;
16 | import org.apache.jmeter.threads.JMeterContext;
17 | import org.apache.jmeter.util.JMeterUtils;
18 | import org.apache.jorphan.logging.LoggingManager;
19 | import org.apache.log.Logger;
20 | import org.jsoup.Connection;
21 | import org.jsoup.Jsoup;
22 | import org.jsoup.nodes.Document;
23 | import org.jsoup.nodes.Element;
24 | import org.jsoup.nodes.FormElement;
25 | import org.jsoup.select.Elements;
26 |
27 | public class HTTPFormManager extends AbstractTestElement implements PreProcessor, TestBean, TestIterationListener {
28 |
29 | private static final long serialVersionUID = 1L;
30 |
31 | public static Logger log = LoggingManager.getLoggerForClass();
32 |
33 | protected SampleResult lastHtmlResult;
34 | protected boolean isNextIteration = false;
35 |
36 | protected String contentType = JMeterUtils.getPropDefault("jmeter.formman.contentType", "text/html");
37 | private boolean clearEachIteration, copyParameters, copyUrl;
38 | private boolean matchSamplerUrl, matchSamplerParameters, matchSubmit;
39 | private String matchCssSelector;
40 |
41 | /** Both parsing responses as well as modifying parameters happens in the
42 | * Preprocessor method (so there is no need for a separate Postprocessor).
43 | * Parsing is only done for a response with the right content type.
44 | */
45 | @Override
46 | public void process() {
47 | JMeterContext context = getThreadContext();
48 | Sampler current = context.getCurrentSampler();
49 | log.debug("Processing sampler \""+current.getName()+"\"");
50 |
51 | // First, irrespective of current sampler, deal with the last result.
52 | // Ignore irrelevant content types, but only store HTML results for the current sampler
53 | // (or any subsequent sampler as there could be non-HTTP samplers in between).
54 | SampleResult prev = context.getPreviousResult(); // should only be null at the very beginning
55 |
56 | if (prev != null && prev.getContentType() != null && prev.getContentType().startsWith(contentType)) {
57 | log.debug("Storing HTML result from \""+prev.getSampleLabel()+"\"");
58 | lastHtmlResult = prev; // retain result across non-HTML samplers
59 | }
60 | // Now, deal with the actual sampler.
61 | // Ignore non-HTTP form post samplers and (optionally) samplers at the start of thread iterations.
62 | if (!(current instanceof HTTPSamplerBase)) {
63 | log.debug("No HTTP sampler, skipping");
64 | return;
65 | }
66 | HTTPSamplerBase sampler = (HTTPSamplerBase)current;
67 | if (sampler.getPostBodyRaw()) {
68 | log.debug("No HTTP Form but raw body, skipping");
69 | return;
70 | }
71 | if (clearEachIteration && isNextIteration) {
72 | log.debug("Clearing form data on iteration start");
73 | isNextIteration = false; // make sure to only skip once i.e. the first HTTP sampler in a thread group
74 | if (lastHtmlResult != null) {
75 | log.debug("Discarding form data from sampler \""+lastHtmlResult.getSampleLabel()+"\"");
76 | }
77 | lastHtmlResult = null;
78 | return;
79 | }
80 | if (lastHtmlResult == null) { // only resource or Ajax requests so far i.e. no form data (or start of new iteration)
81 | log.debug("No stored form data available, skipping");
82 | return;
83 | }
84 | // Now that we've got a previous HTML result we can parse it and find a form
85 | Document document = Jsoup.parse(lastHtmlResult.getResponseDataAsString(), lastHtmlResult.getURL().toString());
86 | FormElement form = findForm(document, sampler);
87 | modifySampler(sampler, form);
88 | }
89 |
90 | protected FormElement findForm(Document document, HTTPSamplerBase sampler) {
91 | // Of all the forms in the parsed HTML document, keep only the matching ones
92 | //noinspection unchecked,rawtypes - as we know that jsoup return FormElements (cannot use forms() due to jsoup bug #1384)
93 | List forms = (List)document.select("form");
94 | forms.removeIf(form -> !isMatch(form, sampler));
95 |
96 | if (forms.size() == 1) {
97 | log.debug("Unique match found");
98 | return forms.get(0);
99 | }
100 |
101 | log.debug((forms.isEmpty() ? "No" : "More than one") + " form match found. No sampler modification.");
102 | return null;
103 | }
104 |
105 | protected boolean isMatch(FormElement form, HTTPSamplerBase sampler) {
106 | log.debug("Trying to match form: "+form.attributes());
107 | if (isMatchSamplerUrl()) {
108 | Connection.Request request = form.submit().request();
109 | // TODO forms with buttons that override these via formaction/formmethod attributes
110 | String formMethod = request.method().toString();
111 | String formUrl = request.url().toString();
112 | String samplerMethod = sampler.getMethod();
113 | String samplerUrl;
114 | try {
115 | samplerUrl = sampler.getUrl().toString();
116 | } catch (MalformedURLException e) {
117 | log.warn("Cannot process sampler! ", e);
118 | return false;
119 | }
120 | if (!samplerMethod.equals(formMethod) || !samplerUrl.equals(formUrl)) {
121 | log.debug("Form does not match sampler URL or method");
122 | return false;
123 | }
124 | }
125 | final Map samplerParameters = sampler.getArguments().getArgumentsAsMap();
126 | if (isMatchSamplerParameters()) {
127 | List formParams = form.formData();
128 | Set samplerParams = samplerParameters.keySet();
129 | if (!formParams.containsAll(samplerParams)) {
130 | log.debug("Sampler parameters do not match");
131 | return false;
132 | }
133 | }
134 | if (isMatchSubmit()) {
135 | boolean submitMatch = false;
136 | for (Element submit : form.select("[type=submit]")) { // TODO look for buttons in entire doc (with form attributes)
137 | String submitName = submit.attr("name");
138 | String submitValue = submit.attr("value");
139 | if (submitValue != null && submitValue.equals(samplerParameters.get(submitName))) {
140 | log.debug("Submit matches a sampler argument name/value: "+submit);
141 | submitMatch = true;
142 | }
143 | }
144 | if (!submitMatch) {
145 | log.debug("Sampler parameters do not match form submit element");
146 | return false;
147 | }
148 | }
149 | String selector = getMatchCssSelector();
150 | if (selector != null && !selector.isEmpty()) {
151 | final Elements el = form.select(selector);
152 | if (el == null || el.isEmpty()) {
153 | log.debug("Form does not match CSS selector");
154 | return false;
155 | }
156 | }
157 | return true;
158 | }
159 |
160 | protected void modifySampler(HTTPSamplerBase sampler, FormElement form) {
161 | if (form == null || sampler == null) return;
162 |
163 | if (isCopyUrl()) {
164 | URL url = form.submit().request().url();
165 | String path = url.getPath(), query = url.getQuery();
166 | if (query != null && !query.isEmpty()) path += query;
167 | log.debug("Copying form URL path "+path);
168 | sampler.setPath(path);
169 | }
170 | if (isCopyParameters()) {
171 | // Make sure not to copy multiple submit elements
172 | if (form.select("[type=submit]").size() > 1) {
173 | log.debug("Form has more than one submit element. Excluding all, assuming sampler has submit element.");
174 | form.elements().removeIf(e -> "submit".equalsIgnoreCase(e.attr("type")));
175 | }
176 | final Map samplerParameters = sampler.getArguments().getArgumentsAsMap();
177 | // Add only form parameters that are not already defined by the user
178 | for (Connection.KeyVal formParam : form.formData()) {
179 | if (!samplerParameters.containsKey(formParam.key())) {
180 | log.debug("Adding "+formParam);
181 | sampler.addArgument(formParam.key(), formParam.value());
182 | }
183 | }
184 | }
185 | }
186 |
187 | @Override
188 | public void testIterationStart(LoopIterationEvent event) {
189 | log.debug("New thread iteration detected");
190 | isNextIteration = true;
191 | }
192 |
193 | // Accessors
194 | public boolean isClearEachIteration() {
195 | return clearEachIteration;
196 | }
197 |
198 | public void setClearEachIteration(boolean clearEachIteration) {
199 | this.clearEachIteration = clearEachIteration;
200 | }
201 |
202 | public boolean isMatchSamplerUrl() {
203 | return matchSamplerUrl;
204 | }
205 |
206 | public void setMatchSamplerUrl(boolean matchSamplerUrl) {
207 | this.matchSamplerUrl = matchSamplerUrl;
208 | }
209 |
210 | public boolean isMatchSamplerParameters() {
211 | return matchSamplerParameters;
212 | }
213 |
214 | public void setMatchSamplerParameters(boolean matchSamplerParameters) {
215 | this.matchSamplerParameters = matchSamplerParameters;
216 | }
217 |
218 | public boolean isMatchSubmit() {
219 | return matchSubmit;
220 | }
221 |
222 | public void setMatchSubmit(boolean matchSubmit) {
223 | this.matchSubmit = matchSubmit;
224 | }
225 |
226 | public String getMatchCssSelector() {
227 | return matchCssSelector;
228 | }
229 |
230 | public void setMatchCssSelector(String matchCssSelector) {
231 | this.matchCssSelector = matchCssSelector;
232 | }
233 |
234 | public boolean isCopyParameters() {
235 | return copyParameters;
236 | }
237 |
238 | public void setCopyParameters(boolean copyParameters) {
239 | this.copyParameters = copyParameters;
240 | }
241 |
242 | public boolean isCopyUrl() {
243 | return copyUrl;
244 | }
245 |
246 | public void setCopyUrl(boolean copyUrl) {
247 | this.copyUrl = copyUrl;
248 | }
249 | }
--------------------------------------------------------------------------------
/src/main/java/nz/co/breakpoint/jmeter/modifiers/HTTPFormManagerBeanInfo.java:
--------------------------------------------------------------------------------
1 | package nz.co.breakpoint.jmeter.modifiers;
2 |
3 | import java.beans.PropertyDescriptor;
4 | import org.apache.jmeter.testbeans.BeanInfoSupport;
5 |
6 | public class HTTPFormManagerBeanInfo extends BeanInfoSupport {
7 |
8 | public HTTPFormManagerBeanInfo() {
9 | super(HTTPFormManager.class);
10 |
11 | createPropertyGroup("Options", new String[]{ "clearEachIteration" });
12 | createPropertyGroup("FormIdentification", new String[]{ "matchSamplerUrl", "matchSamplerParameters", "matchSubmit", "matchCssSelector" });
13 | createPropertyGroup("SamplerModification", new String[]{ "copyParameters", "copyUrl" });
14 | PropertyDescriptor p;
15 |
16 | p = property("clearEachIteration");
17 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
18 | p.setValue(DEFAULT, true);
19 |
20 | p = property("matchSamplerUrl");
21 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
22 | p.setValue(DEFAULT, true);
23 |
24 | p = property("matchSamplerParameters");
25 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
26 | p.setValue(DEFAULT, false);
27 |
28 | p = property("matchSubmit");
29 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
30 | p.setValue(DEFAULT, false);
31 |
32 | p = property("matchCssSelector");
33 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
34 | p.setValue(DEFAULT, "");
35 |
36 | p = property("copyParameters");
37 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
38 | p.setValue(DEFAULT, true);
39 |
40 | p = property("copyUrl");
41 | p.setValue(NOT_UNDEFINED, Boolean.TRUE);
42 | p.setValue(DEFAULT, false);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/resources/nz/co/breakpoint/jmeter/modifiers/HTTPFormManagerResources.properties:
--------------------------------------------------------------------------------
1 | displayName=HTTP Form Manager
2 | Options.displayName=Options
3 | clearEachIteration.displayName=Clear form data each iteration?
4 | clearEachIteration.shortDescription=Whether to discard any stored form input data when starting a new thread iteration
5 | FormIdentification.displayName=Form Identification
6 | FormIdentification.shortDescription=How to select which form to copy parameters from (if multiple forms exist in response)
7 | matchSamplerUrl.displayName=By sampler URL
8 | matchSamplerUrl.shortDescription=Form must contain the current sampler's URL and method
9 | matchSamplerParameters.displayName=By sampler parameters
10 | matchSamplerParameters.shortDescription=For must contain the current sampler's parameter names
11 | matchSubmit.displayName=By submit element
12 | matchSubmit.shortDescription=Form must contain a submit element matching one of the current sampler's parameter (name and value)
13 | matchCssSelector.displayName=By CSS selector
14 | matchCssSelector.shortDescription=Form must match this CSS selector expression (unless empty)
15 | SamplerModification.displayName=Sampler Modification
16 | copyParameters.displayName=Copy form parameters
17 | copyParameters.shortDescription=Copy form parameters from previous HTML response to current sampler
18 | copyUrl.displayName=Copy form URL
19 | copyUrl.shortDescription=Extract form URL from previous HTML response and assign to current sampler
20 |
--------------------------------------------------------------------------------
/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestHTTPFormManager.java:
--------------------------------------------------------------------------------
1 | package nz.co.breakpoint.jmeter.modifiers;
2 |
3 | import java.net.URL;
4 | import java.util.Map;
5 | import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
6 | import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory;
7 | import org.apache.jmeter.samplers.SampleResult;
8 | import org.apache.jmeter.threads.JMeterContext;
9 | import org.apache.jmeter.threads.JMeterContextService;
10 | import org.hamcrest.collection.IsMapContaining;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 |
14 | import static org.hamcrest.CoreMatchers.is;
15 | import static org.junit.Assert.assertEquals;
16 | import static org.junit.Assert.assertThat;
17 |
18 | public class TestHTTPFormManager {
19 | protected JMeterContext context;
20 | protected HTTPSamplerBase sampler;
21 | protected SampleResult prev;
22 | protected HTTPFormManager instance;
23 |
24 | protected final static String html =
25 | ""+
26 | " "+
27 | " "+
32 | " "+
38 | " "+
41 | " "+
44 | " "+
48 | " "+
52 | " "+
55 | " "+
56 | "";
57 |
58 | @Before
59 | public void setUp() throws Exception {
60 | sampler = HTTPSamplerFactory.newInstance();
61 | sampler.setProtocol("http");
62 | sampler.setDomain("dummy.net");
63 | sampler.setPath("/base/form");
64 | sampler.setMethod("POST");
65 |
66 | prev = SampleResult.createTestSample(0);
67 | prev.setURL(new URL("http://dummy.net/base/form"));
68 | prev.setContentType("text/html");
69 | prev.setResponseData(html, "UTF-8");
70 |
71 | context = JMeterContextService.getContext();
72 | context.setCurrentSampler(sampler);
73 | context.setPreviousResult(prev);
74 |
75 | instance = new HTTPFormManager();
76 | instance.setThreadContext(context);
77 | instance.setCopyParameters(true);
78 | instance.setMatchSamplerUrl(true);
79 | }
80 |
81 | @Test
82 | public void testNoModificationIfNoFormMatches() throws Exception {
83 | instance.log.info("testNoModificationIfNoFormMatches");
84 | sampler.setPath("/base/no-match");
85 | sampler.addArgument("name", "value");
86 | instance.process();
87 | Map args = sampler.getArguments().getArgumentsAsMap();
88 | assertThat(args.size(), is(1));
89 | assertThat(args, IsMapContaining.hasEntry("name", "value"));
90 | }
91 |
92 | @Test
93 | public void testFormIsSelectedByMethodAndURL() throws Exception {
94 | instance.log.info("testFormIsSelectedByMethodAndURL");
95 | sampler.setMethod("GET"); // matches form 3
96 | instance.process();
97 | Map args = sampler.getArguments().getArgumentsAsMap();
98 | assertThat(args.size(), is(1));
99 | assertThat(args, IsMapContaining.hasEntry("hidden_input", "hidden_value3"));
100 | }
101 |
102 | @Test
103 | public void testFormIsSelectedByMethodAndURLWithQueryParameters() throws Exception {
104 | instance.log.info("testFormIsSelectedByMethodAndURLWithQueryParameters");
105 | sampler.setPath("/base/form?queryparameter=value");
106 | instance.process();
107 | Map args = sampler.getArguments().getArgumentsAsMap();
108 | assertThat(args.size(), is(1));
109 | assertThat(args, IsMapContaining.hasEntry("submit", "submit_value7"));
110 | }
111 |
112 | @Test
113 | public void testNoModificationToExplicitValue() throws Exception {
114 | instance.log.info("testNoModificationToExplicitValue");
115 | sampler.setPath("/other-form");
116 | sampler.addArgument("text_input", "explicit_value");
117 | instance.process();
118 | Map args = sampler.getArguments().getArgumentsAsMap();
119 | assertThat(args.size(), is(2));
120 | assertThat(args, IsMapContaining.hasEntry("hidden_input", "hidden_value"));
121 | assertThat(args, IsMapContaining.hasEntry("text_input", "explicit_value"));
122 | }
123 |
124 | @Test
125 | public void testFormIsSelectedByExplicitSubmit() throws Exception {
126 | instance.log.info("testFormIsSelectedByExplicitSubmit");
127 | instance.setMatchSamplerUrl(false);
128 | instance.setMatchSubmit(true);
129 | sampler.addArgument("submit", "submit_value3");
130 | instance.process();
131 | Map args = sampler.getArguments().getArgumentsAsMap();
132 | assertThat(args.size(), is(2));
133 | assertThat(args, IsMapContaining.hasEntry("submit", "submit_value3"));
134 | assertThat(args, IsMapContaining.hasEntry("hidden_input", "hidden_value2"));
135 | }
136 |
137 | @Test
138 | public void testFormIsSelectedByExplicitSelector() throws Exception {
139 | instance.log.info("testFormIsSelectedByExplicitSelector");
140 | instance.setMatchSamplerUrl(false);
141 | instance.setMatchCssSelector("form[name=6]");
142 | instance.process();
143 | Map args = sampler.getArguments().getArgumentsAsMap();
144 | assertThat(args.size(), is(2));
145 | assertThat(args, IsMapContaining.hasEntry("hidden_input", "hidden_value"));
146 | assertThat(args, IsMapContaining.hasEntry("text_input", "text_value"));
147 | }
148 |
149 | @Test
150 | public void testUrlIsModified() throws Exception {
151 | instance.log.info("testUrlIsModified");
152 | instance.setMatchSamplerUrl(false);
153 | instance.setMatchCssSelector("form[name=6]");
154 | instance.setCopyUrl(true);
155 | instance.process();
156 | String samplerUrl = sampler.getUrl().toString();
157 | assertEquals(samplerUrl, "http://dummy.net/other-form");
158 | }
159 |
160 | @Test
161 | public void testNoModificationIfAmbiguous() throws Exception {
162 | instance.log.info("testNoModificationIfAmbiguous");
163 | sampler.addArgument("submit", "submit_value2");
164 | instance.process();
165 | Map args = sampler.getArguments().getArgumentsAsMap();
166 | assertThat(args.size(), is(1));
167 | assertThat(args, IsMapContaining.hasEntry("submit", "submit_value2"));
168 | }
169 | }
170 |
--------------------------------------------------------------------------------