View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import java.net.URL;
18  import java.util.Arrays;
19  import java.util.Collections;
20  import java.util.List;
21  
22  import org.htmlunit.MockWebConnection;
23  import org.htmlunit.WebDriverTestCase;
24  import org.htmlunit.junit.annotation.Alerts;
25  import org.htmlunit.util.NameValuePair;
26  import org.junit.jupiter.api.Test;
27  import org.openqa.selenium.By;
28  import org.openqa.selenium.WebDriver;
29  import org.openqa.selenium.WebElement;
30  
31  /**
32   * Tests for {@link HtmlSubmitInput}.
33   *
34   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
35   * @author Marc Guillemot
36   * @author Ahmed Ashour
37   * @author Ronald Brill
38   * @author Frank Danek
39   */
40  public class HtmlSubmitInputTest extends WebDriverTestCase {
41  
42      /**
43       * @throws Exception if the test fails
44       */
45      @Test
46      public void submit() throws Exception {
47          final String html = DOCTYPE_HTML
48              + "<html><head><title>foo</title></head><body>\n"
49              + "<form id='form1' method='post'>\n"
50              + "<input type='submit' name='aButton' value='foo'/>\n"
51              + "<input type='suBMit' name='button' value='foo'/>\n"
52              + "<input type='submit' name='anotherButton' value='foo'/>\n"
53              + "</form></body></html>";
54  
55          final WebDriver driver = loadPage2(html);
56  
57          final WebElement button = driver.findElement(By.name("button"));
58          button.click();
59  
60          assertTitle(driver, "foo");
61  
62          assertEquals(Collections.singletonList(new NameValuePair("button", "foo")),
63              getMockWebConnection().getLastParameters());
64      }
65  
66      /**
67       * @throws Exception if the test fails
68       */
69      @Test
70      @Alerts({"foo", "bar"})
71      public void click_onClick() throws Exception {
72          final String html = DOCTYPE_HTML
73              + "<html><head>"
74              + "<script>\n"
75              + LOG_TITLE_FUNCTION
76              + "</script>\n"
77              + "</head><body>\n"
78              + "<form id='form1' onSubmit='log(\"bar\"); return false;'>\n"
79              + "  <input type='submit' name='button' value='foo' onClick='log(\"foo\")'/>\n"
80              + "</form></body></html>";
81  
82          final WebDriver webDriver = loadPage2(html);
83  
84          final WebElement button = webDriver.findElement(By.name("button"));
85          button.click();
86  
87          verifyTitle2(webDriver, getExpectedAlerts());
88      }
89  
90      /**
91       * @throws Exception if the test fails
92       */
93      @Test
94      public void click_onClick_JavascriptReturnsTrue() throws Exception {
95          final String html = DOCTYPE_HTML
96              + "<html><head></head><body>\n"
97              + "<form name='form1' method='get' action='foo.html'>\n"
98              + "<input name='button' type='submit' value='PushMe' id='button1'"
99              + "onclick='return true'/></form>\n"
100             + "</body></html>";
101         final String secondHtml = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
102 
103         getMockWebConnection().setResponse(new URL(URL_FIRST, "foo.html"), secondHtml);
104 
105         final WebDriver driver = loadPage2(html);
106 
107         final WebElement button = driver.findElement(By.id("button1"));
108         button.click();
109 
110         assertTitle(driver, "Second");
111     }
112 
113     /**
114      * @throws Exception if the test fails
115      */
116     @Test
117     @Alerts("1")
118     public void outsideForm() throws Exception {
119         final String html = DOCTYPE_HTML
120             + "<html><head>\n"
121             + "<script>\n"
122             + LOG_TITLE_FUNCTION
123             + "</script>\n"
124             + "</head>\n"
125             + "<body>\n"
126             + "<input id='myInput' type='submit' onclick='log(1)'>\n"
127             + "</body></html>";
128 
129         final WebDriver webDriver = loadPage2(html);
130         final WebElement input = webDriver.findElement(By.id("myInput"));
131         input.click();
132 
133         verifyTitle2(webDriver, getExpectedAlerts());
134     }
135 
136     /**
137      * @throws Exception if the test fails
138      */
139     @Test
140     @Alerts("1")
141     public void onclickDisables() throws Exception {
142         final String html = DOCTYPE_HTML
143             + "<html>\n"
144             + "<head>\n"
145             + "  <script type='text/javascript'>\n"
146             + "    function submitForm() {\n"
147             + "      document.deliveryChannelForm.submitBtn.disabled = true;\n"
148             + "    }\n"
149             + "  </script>\n"
150             + "</head>\n"
151             + "<body>\n"
152             + "  <form action='test' name='deliveryChannelForm'>\n"
153             + "    <input name='submitBtn' type='submit' value='Save' title='Save' onclick='submitForm();'>\n"
154             + "  </form>\n"
155             + "</body>\n"
156             + "</html>";
157 
158         getMockWebConnection().setDefaultResponse("");
159         final WebDriver webDriver = loadPage2(html);
160         final WebElement input = webDriver.findElement(By.name("submitBtn"));
161         input.click();
162 
163         assertEquals(Integer.parseInt(getExpectedAlerts()[0]), getMockWebConnection().getRequestCount());
164     }
165 
166     /**
167      * @throws Exception if the test fails
168      */
169     @Test
170     @Alerts("2")
171     public void doubleSubmission() throws Exception {
172         final String html = DOCTYPE_HTML
173             + "<html>\n"
174             + "<head>\n"
175             + "  <script type='text/javascript'>\n"
176             + "    function submitForm() {\n"
177             + "      document.deliveryChannelForm.submitBtn.disabled = true;\n"
178             + "      document.deliveryChannelForm.submit();\n"
179             + "    }\n"
180             + "  </script>\n"
181             + "</head>\n"
182             + "<body>\n"
183             + "  <form action='test' name='deliveryChannelForm'>\n"
184             + "    <input name='submitBtn' type='submit' value='Save' title='Save' onclick='submitForm();'>\n"
185             + "  </form>\n"
186             + "</body>\n"
187             + "</html>";
188 
189         getMockWebConnection().setDefaultResponse("");
190         final WebDriver webDriver = loadPage2(html);
191         final WebElement input = webDriver.findElement(By.name("submitBtn"));
192         input.click();
193 
194         assertEquals(Integer.parseInt(getExpectedAlerts()[0]), getMockWebConnection().getRequestCount());
195     }
196 
197     /**
198      * @throws Exception if the test fails
199      */
200     @Test
201     public void doubleSubmissionWithRedirect() throws Exception {
202         final String html = DOCTYPE_HTML
203             + "<html><body>\n"
204             + "<script>\n"
205             + "function submitForm(btn) {\n"
206             + "  btn.form.submit();\n"
207             + "  btn.disabled = true;\n"
208             + "}\n"
209             + "</script>\n"
210             + "<form method='post' action='test'>\n"
211             + "  <input name='text' type='text'>\n"
212             + "  <input name='btn' type='submit' onclick='submitForm(this);'>\n"
213             + "</form>\n"
214             + "</body></html>";
215 
216         final MockWebConnection mockWebConnection = getMockWebConnection();
217 
218         final List<NameValuePair> redirectHeaders = Arrays.asList(new NameValuePair("Location", "/nextPage"));
219         mockWebConnection.setResponse(new URL(URL_FIRST, "test"), "", 302, "Found", null, redirectHeaders);
220 
221         mockWebConnection.setResponse(new URL(URL_FIRST, "nextPage"),
222                 DOCTYPE_HTML + "<html><head><title>next page</title></head></html>");
223 
224         final WebDriver driver = loadPage2(html);
225         final WebElement input = driver.findElement(By.name("btn"));
226         input.click();
227 
228         assertTitle(driver, "next page");
229         assertEquals(3, mockWebConnection.getRequestCount());
230     }
231 
232     /**
233      * @throws Exception if the test fails
234      */
235     @Test
236     @Alerts({"-", "-", "-"})
237     public void defaultValues() throws Exception {
238         final String html = DOCTYPE_HTML
239             + "<html><head>\n"
240             + "<script>\n"
241             + LOG_TITLE_FUNCTION
242             + "  function test() {\n"
243             + "    var input = document.getElementById('submit1');\n"
244             + "    log(input.value + '-' + input.defaultValue);\n"
245 
246             + "    input = document.createElement('input');\n"
247             + "    input.type = 'submit';\n"
248             + "    log(input.value + '-' + input.defaultValue);\n"
249 
250             + "    var builder = document.createElement('div');\n"
251             + "    builder.innerHTML = '<input type=\"submit\">';\n"
252             + "    input = builder.firstChild;\n"
253             + "    log(input.value + '-' + input.defaultValue);\n"
254             + "  }\n"
255             + "</script>\n"
256             + "</head><body onload='test()'>\n"
257             + "<form>\n"
258             + "  <input type='submit' id='submit1'>\n"
259             + "</form>\n"
260             + "</body></html>";
261 
262         loadPageVerifyTitle2(html);
263     }
264 
265     /**
266      * @throws Exception if the test fails
267      */
268     @Test
269     @Alerts({"-", "-", "-"})
270     public void defaultValuesAfterClone() throws Exception {
271         final String html = DOCTYPE_HTML
272             + "<html><head>\n"
273             + "<script>\n"
274             + LOG_TITLE_FUNCTION
275             + "  function test() {\n"
276             + "    var input = document.getElementById('submit1');\n"
277             + "    input = input.cloneNode(false);\n"
278             + "    log(input.value + '-' + input.defaultValue);\n"
279 
280             + "    input = document.createElement('input');\n"
281             + "    input.type = 'submit';\n"
282             + "    input = input.cloneNode(false);\n"
283             + "    log(input.value + '-' + input.defaultValue);\n"
284 
285             + "    var builder = document.createElement('div');\n"
286             + "    builder.innerHTML = '<input type=\"submit\">';\n"
287             + "    input = builder.firstChild;\n"
288             + "    input = input.cloneNode(false);\n"
289             + "    log(input.value + '-' + input.defaultValue);\n"
290             + "  }\n"
291             + "</script>\n"
292             + "</head><body onload='test()'>\n"
293             + "<form>\n"
294             + "  <input type='submit' id='submit1'>\n"
295             + "</form>\n"
296             + "</body></html>";
297 
298         loadPageVerifyTitle2(html);
299     }
300 
301     /**
302      * @throws Exception if the test fails
303      */
304     @Test
305     @Alerts({"initial-initial", "initial-initial", "newValue-newValue", "newValue-newValue",
306                 "newDefault-newDefault", "newDefault-newDefault"})
307     public void resetByClick() throws Exception {
308         final String html = DOCTYPE_HTML
309             + "<html><head>\n"
310             + "<script>\n"
311             + LOG_TITLE_FUNCTION
312             + "  function test() {\n"
313             + "    var submit = document.getElementById('testId');\n"
314             + "    log(submit.value + '-' + submit.defaultValue);\n"
315 
316             + "    document.getElementById('testReset').click;\n"
317             + "    log(submit.value + '-' + submit.defaultValue);\n"
318 
319             + "    submit.value = 'newValue';\n"
320             + "    log(submit.value + '-' + submit.defaultValue);\n"
321 
322             + "    document.getElementById('testReset').click;\n"
323             + "    log(submit.value + '-' + submit.defaultValue);\n"
324 
325             + "    submit.defaultValue = 'newDefault';\n"
326             + "    log(submit.value + '-' + submit.defaultValue);\n"
327 
328             + "    document.forms[0].reset;\n"
329             + "    log(submit.value + '-' + submit.defaultValue);\n"
330             + "  }\n"
331             + "</script>\n"
332             + "</head><body onload='test()'>\n"
333             + "<form>\n"
334             + "  <input type='submit' id='testId' value='initial'>\n"
335             + "  <input type='reset' id='testReset'>\n"
336             + "</form>\n"
337             + "</body></html>";
338 
339         loadPageVerifyTitle2(html);
340     }
341 
342     /**
343      * @throws Exception if the test fails
344      */
345     @Test
346     @Alerts({"initial-initial", "initial-initial", "newValue-newValue", "newValue-newValue",
347                 "newDefault-newDefault", "newDefault-newDefault"})
348     public void resetByJS() throws Exception {
349         final String html = DOCTYPE_HTML
350             + "<html><head>\n"
351             + "<script>\n"
352             + LOG_TITLE_FUNCTION
353             + "  function test() {\n"
354             + "    var submit = document.getElementById('testId');\n"
355             + "    log(submit.value + '-' + submit.defaultValue);\n"
356 
357             + "    document.forms[0].reset;\n"
358             + "    log(submit.value + '-' + submit.defaultValue);\n"
359 
360             + "    submit.value = 'newValue';\n"
361             + "    log(submit.value + '-' + submit.defaultValue);\n"
362 
363             + "    document.forms[0].reset;\n"
364             + "    log(submit.value + '-' + submit.defaultValue);\n"
365 
366             + "    submit.defaultValue = 'newDefault';\n"
367             + "    log(submit.value + '-' + submit.defaultValue);\n"
368 
369             + "    document.forms[0].reset;\n"
370             + "    log(submit.value + '-' + submit.defaultValue);\n"
371             + "  }\n"
372             + "</script>\n"
373             + "</head><body onload='test()'>\n"
374             + "<form>\n"
375             + "  <input type='submit' id='testId' value='initial'>\n"
376             + "</form>\n"
377             + "</body></html>";
378 
379         loadPageVerifyTitle2(html);
380     }
381 
382     /**
383      * @throws Exception if the test fails
384      */
385     @Test
386     @Alerts({"initial-initial", "default-default", "newValue-newValue", "newdefault-newdefault"})
387     public void defaultValue() throws Exception {
388         final String html = DOCTYPE_HTML
389             + "<html><head>\n"
390             + "<script>\n"
391             + LOG_TITLE_FUNCTION
392             + "  function test() {\n"
393             + "    var submit = document.getElementById('testId');\n"
394             + "    log(submit.value + '-' + submit.defaultValue);\n"
395 
396             + "    submit.defaultValue = 'default';\n"
397             + "    log(submit.value + '-' + submit.defaultValue);\n"
398 
399             + "    submit.value = 'newValue';\n"
400             + "    log(submit.value + '-' + submit.defaultValue);\n"
401             + "    submit.defaultValue = 'newdefault';\n"
402             + "    log(submit.value + '-' + submit.defaultValue);\n"
403             + "  }\n"
404             + "</script>\n"
405             + "</head><body onload='test()'>\n"
406             + "<form>\n"
407             + "  <input type='submit' id='testId' value='initial'>\n"
408             + "</form>\n"
409             + "</body></html>";
410 
411         loadPageVerifyTitle2(html);
412     }
413 
414     /**
415      * @throws Exception if the test fails
416      */
417     @Test
418     @Alerts("--")
419     public void minMaxStep() throws Exception {
420         final String html = DOCTYPE_HTML
421             + "<html>\n"
422             + "<head>\n"
423             + "<script>\n"
424             + LOG_TITLE_FUNCTION
425             + "  function test() {\n"
426             + "    var input = document.getElementById('tester');\n"
427             + "    log(input.min + '-' + input.max + '-' + input.step);\n"
428             + "  }\n"
429             + "</script>\n"
430             + "</head>\n"
431             + "<body onload='test()'>\n"
432             + "<form>\n"
433             + "  <input type='submit' id='tester'>\n"
434             + "</form>\n"
435             + "</body>\n"
436             + "</html>";
437 
438         loadPageVerifyTitle2(html);
439     }
440 
441     /**
442      * @throws Exception if an error occurs
443      */
444     @Test
445     @Alerts({"true", "false", "true", "false", "true"})
446     public void willValidate() throws Exception {
447         final String html = DOCTYPE_HTML
448                 + "<html><head>\n"
449                 + "  <script>\n"
450                 + LOG_TITLE_FUNCTION
451                 + "    function test() {\n"
452                 + "      log(document.getElementById('o1').willValidate);\n"
453                 + "      log(document.getElementById('o2').willValidate);\n"
454                 + "      log(document.getElementById('o3').willValidate);\n"
455                 + "      log(document.getElementById('o4').willValidate);\n"
456                 + "      log(document.getElementById('o5').willValidate);\n"
457                 + "    }\n"
458                 + "  </script>\n"
459                 + "</head>\n"
460                 + "<body onload='test()'>\n"
461                 + "  <form>\n"
462                 + "    <input type='submit' id='o1'>\n"
463                 + "    <input type='submit' id='o2' disabled>\n"
464                 + "    <input type='submit' id='o3' hidden>\n"
465                 + "    <input type='submit' id='o4' readonly>\n"
466                 + "    <input type='submit' id='o5' style='display: none'>\n"
467                 + "  </form>\n"
468                 + "</body></html>";
469 
470         loadPageVerifyTitle2(html);
471     }
472 
473     /**
474      * @throws Exception if an error occurs
475      */
476     @Test
477     @Alerts({"true",
478              "false-false-false-false-false-false-false-false-false-true-false",
479              "true"})
480     public void validationEmpty() throws Exception {
481         validation("<input type='submit' id='e1'>\n", "");
482     }
483 
484     /**
485      * @throws Exception if an error occurs
486      */
487     @Test
488     @Alerts({"false",
489              "false-true-false-false-false-false-false-false-false-false-false",
490              "true"})
491     public void validationCustomValidity() throws Exception {
492         validation("<input type='submit' id='e1'>\n", "elem.setCustomValidity('Invalid');");
493     }
494 
495     /**
496      * @throws Exception if an error occurs
497      */
498     @Test
499     @Alerts({"false",
500              "false-true-false-false-false-false-false-false-false-false-false",
501              "true"})
502     public void validationBlankCustomValidity() throws Exception {
503         validation("<input type='submit' id='e1'>\n", "elem.setCustomValidity(' ');\n");
504     }
505 
506     /**
507      * @throws Exception if an error occurs
508      */
509     @Test
510     @Alerts({"true",
511              "false-false-false-false-false-false-false-false-false-true-false",
512              "true"})
513     public void validationResetCustomValidity() throws Exception {
514         validation("<input type='submit' id='e1'>\n",
515                 "elem.setCustomValidity('Invalid');elem.setCustomValidity('');");
516     }
517 
518     /**
519      * @throws Exception if an error occurs
520      */
521     @Test
522     @Alerts({"true",
523              "false-false-false-false-false-false-false-false-false-true-false",
524              "true"})
525     public void validationRequired() throws Exception {
526         validation("<input type='submit' id='e1' required>\n", "");
527     }
528 
529     private void validation(final String htmlPart, final String jsPart) throws Exception {
530         final String html = DOCTYPE_HTML
531                 + "<html><head>\n"
532                 + "  <script>\n"
533                 + LOG_TITLE_FUNCTION
534                 + "    function logValidityState(s) {\n"
535                 + "      log(s.badInput"
536                         + "+ '-' + s.customError"
537                         + "+ '-' + s.patternMismatch"
538                         + "+ '-' + s.rangeOverflow"
539                         + "+ '-' + s.rangeUnderflow"
540                         + "+ '-' + s.stepMismatch"
541                         + "+ '-' + s.tooLong"
542                         + "+ '-' + s.tooShort"
543                         + " + '-' + s.typeMismatch"
544                         + " + '-' + s.valid"
545                         + " + '-' + s.valueMissing);\n"
546                 + "    }\n"
547                 + "    function test() {\n"
548                 + "      var elem = document.getElementById('e1');\n"
549                 + jsPart
550                 + "      log(elem.checkValidity());\n"
551                 + "      logValidityState(elem.validity);\n"
552                 + "      log(elem.willValidate);\n"
553                 + "    }\n"
554                 + "  </script>\n"
555                 + "</head>\n"
556                 + "<body onload='test()'>\n"
557                 + "  <form>\n"
558                 + htmlPart
559                 + "  </form>\n"
560                 + "</body></html>";
561 
562         loadPageVerifyTitle2(html);
563     }
564 }