├── .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 | ![HTTP Form Manager](https://raw.githubusercontent.com/tilln/jmeter-formman/master/docs/before.png) 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 | ![HTTP Form Manager](https://raw.githubusercontent.com/tilln/jmeter-formman/master/docs/after.png) 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 |
53 | 54 | 55 | 56 | 57 | ... 58 | 59 | 60 | ... 61 | 62 |
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 |
91 | 92 | ... 93 | 94 |
95 |
96 | 97 | ... 98 | 99 |
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 |
119 | 120 | ... 121 | 122 | 123 |
124 |
125 | 126 | ... 127 | 128 | 129 |
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 |
152 | 153 | ... 154 | 155 | 156 |
157 |
158 | 159 | ... 160 | 161 | 162 |
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 |
184 | 185 | ... 186 | 187 |
188 |
189 | 190 | ... 191 | 192 |
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 | ![Configuration](https://raw.githubusercontent.com/tilln/jmeter-formman/master/docs/config.png) 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 | "
"+ 28 | " "+ 29 | " "+ 30 | " "+ 31 | "
"+ 32 | "
"+ 33 | " "+ 34 | " "+ 35 | " "+ 36 | " "+ 37 | "
"+ 38 | "
"+ // GET is implicit 39 | " "+ 40 | "
"+ 41 | "
"+ 42 | " "+ 43 | "
"+ 44 | "
"+ 45 | " "+ 46 | " "+ 47 | "
"+ 48 | "
"+ 49 | " "+ 50 | " "+ 51 | "
"+ 52 | "
"+ 53 | " "+ 54 | "
"+ 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 | --------------------------------------------------------------------------------