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 org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.annotation.Alerts;
19  import org.junit.jupiter.api.Test;
20  import org.openqa.selenium.By;
21  import org.openqa.selenium.Keys;
22  import org.openqa.selenium.WebDriver;
23  import org.openqa.selenium.WebElement;
24  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
25  
26  /**
27   * Tests for {@link HtmlSearchInput}.
28   *
29   * @author Marc Guillemot
30   * @author Anton Demydenko
31   * @author Ronald Brill
32   */
33  public class HtmlSearchInputTest extends WebDriverTestCase {
34  
35      /**
36       * @throws Exception if the test fails
37       */
38      @Test
39      public void type() throws Exception {
40          final String html = DOCTYPE_HTML + "<html><head></head><body><input id='t' type='search'/></body></html>";
41  
42          final WebDriver webDriver = loadPage2(html);
43          final WebElement input = webDriver.findElement(By.id("t"));
44  
45          input.sendKeys("abc");
46          assertNull(input.getDomAttribute("value"));
47          assertEquals("abc", input.getDomProperty("value"));
48  
49          input.sendKeys(Keys.BACK_SPACE);
50          assertNull(input.getDomAttribute("value"));
51          assertEquals("ab", input.getDomProperty("value"));
52  
53          input.sendKeys(Keys.BACK_SPACE);
54          assertNull(input.getDomAttribute("value"));
55          assertEquals("a", input.getDomProperty("value"));
56  
57          input.sendKeys(Keys.BACK_SPACE);
58          assertNull(input.getDomAttribute("value"));
59          assertEquals("", input.getDomProperty("value"));
60  
61          input.sendKeys(Keys.BACK_SPACE);
62          assertNull(input.getDomAttribute("value"));
63          assertEquals("", input.getDomProperty("value"));
64      }
65  
66      /**
67       * @throws Exception if an error occurs
68       */
69      @Test
70      @Alerts({"--null", "--null", "--null"})
71      public void defaultValuesAfterClone() throws Exception {
72          final String html = DOCTYPE_HTML
73              + "<html><head>\n"
74              + "<script>\n"
75              + LOG_TITLE_FUNCTION
76              + "  function test() {\n"
77              + "    var input = document.getElementById('text1');\n"
78              + "    input = input.cloneNode(false);\n"
79              + "    log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
80  
81              + "    try {\n"
82              + "      input = document.createElement('input');\n"
83              + "      input.type = 'search';\n"
84              + "      input = input.cloneNode(false);\n"
85              + "      log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
86              + "    } catch(e)  { logEx(e); }\n"
87  
88              + "    var builder = document.createElement('div');\n"
89              + "    builder.innerHTML = '<input type=\"search\">';\n"
90              + "    input = builder.firstChild;\n"
91              + "    input = input.cloneNode(false);\n"
92              + "    log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
93              + "  }\n"
94              + "</script>\n"
95              + "</head><body onload='test()'>\n"
96              + "<form>\n"
97              + "  <input type='search' id='text1'>\n"
98              + "</form>\n"
99              + "</body></html>";
100 
101         loadPageVerifyTitle2(html);
102     }
103 
104     /**
105      * Verifies getVisibleText().
106      * @throws Exception if the test fails
107      */
108     @Test
109     @Alerts("")
110     public void getVisibleText() throws Exception {
111         final String htmlContent = DOCTYPE_HTML
112             + "<html>\n"
113             + "<head></head>\n"
114             + "<body>\n"
115             + "<form id='form1'>\n"
116             + "  <input type='search' name='tester' id='tester' value='1234567' >\n"
117             + "</form>\n"
118             + "</body></html>";
119 
120         final WebDriver driver = loadPage2(htmlContent);
121         final String text = driver.findElement(By.id("tester")).getText();
122         assertEquals(getExpectedAlerts()[0], text);
123 
124         if (driver instanceof HtmlUnitDriver) {
125             final HtmlPage page = (HtmlPage) getEnclosedPage();
126             assertEquals(getExpectedAlerts()[0], page.getBody().getVisibleText());
127         }
128     }
129 
130     /**
131      * Verifies clear().
132      * @throws Exception if the test fails
133      */
134     @Test
135     @Alerts({"1234567", ""})
136     public void clearInput() throws Exception {
137         final String htmlContent = DOCTYPE_HTML
138                 + "<html>\n"
139                 + "<head></head>\n"
140                 + "<body>\n"
141                 + "<form id='form1'>\n"
142                 + "  <input type='search' name='tester' id='tester' value='1234567'>\n"
143                 + "</form>\n"
144                 + "</body></html>";
145 
146         final WebDriver driver = loadPage2(htmlContent);
147         final WebElement element = driver.findElement(By.id("tester"));
148 
149         assertEquals(getExpectedAlerts()[0], element.getDomAttribute("value"));
150         assertEquals(getExpectedAlerts()[0], element.getDomProperty("value"));
151 
152         element.clear();
153         assertEquals(getExpectedAlerts()[0], element.getDomAttribute("value"));
154         assertEquals(getExpectedAlerts()[1], element.getDomProperty("value"));
155     }
156 
157     /**
158      * @throws Exception if the test fails
159      */
160     @Test
161     public void typing() throws Exception {
162         final String htmlContent = DOCTYPE_HTML
163             + "<html><head><title>foo</title></head><body>\n"
164             + "<form id='form1'>\n"
165             + "  <input type='search' id='foo'>\n"
166             + "</form></body></html>";
167 
168         final WebDriver driver = loadPage2(htmlContent);
169 
170         final WebElement input = driver.findElement(By.id("foo"));
171 
172         input.sendKeys("hello");
173         assertNull(input.getDomAttribute("value"));
174         assertEquals("hello", input.getDomProperty("value"));
175     }
176 
177     /**
178      * @throws Exception if the test fails
179      */
180     @Test
181     @Alerts("--")
182     public void minMaxStep() throws Exception {
183         final String html = DOCTYPE_HTML
184             + "<html>\n"
185             + "<head>\n"
186             + "<script>\n"
187             + LOG_TITLE_FUNCTION
188             + "  function test() {\n"
189             + "    var input = document.getElementById('tester');\n"
190             + "    log(input.min + '-' + input.max + '-' + input.step);\n"
191             + "  }\n"
192             + "</script>\n"
193             + "</head>\n"
194             + "<body onload='test()'>\n"
195             + "<form>\n"
196             + "  <input type='search' id='tester'>\n"
197             + "</form>\n"
198             + "</body>\n"
199             + "</html>";
200 
201         loadPageVerifyTitle2(html);
202     }
203 
204     /**
205      * @throws Exception if an error occurs
206      */
207     @Test
208     @Alerts({"0987654321!",
209              "0987654321!",
210              "false",
211              "false-false-true-false-false-false-false-false-false-false-false",
212              "true",
213              "§§URL§§", "1"})
214     public void patternValidationInvalid() throws Exception {
215         validation("<input type='search' pattern='[0-9a-zA-Z]{10,40}' id='e1' value='0987654321!' name='k'>\n",
216                     "", null);
217     }
218 
219     /**
220      * @throws Exception if an error occurs
221      */
222     @Test
223     @Alerts({"68746d6c756e69742072756c657a21",
224              "68746d6c756e69742072756c657a21",
225              "true",
226              "false-false-false-false-false-false-false-false-false-true-false",
227              "true",
228              "§§URL§§?k=68746d6c756e69742072756c657a21", "2"})
229     public void patternValidationValid() throws Exception {
230         validation("<input type='search' pattern='[0-9a-zA-Z]{10,40}' "
231                 + "id='e1' value='68746d6c756e69742072756c657a21' name='k'>\n", "", null);
232     }
233 
234     /**
235      * @throws Exception if an error occurs
236      */
237     @Test
238     @Alerts({"",
239              "",
240              "true",
241              "false-false-false-false-false-false-false-false-false-true-false",
242              "true",
243              "§§URL§§?k=", "2"})
244     public void patternValidationEmpty() throws Exception {
245         validation("<input type='search' pattern='[0-9a-zA-Z]{10,40}' id='e1' value='' name='k'>\n", "", null);
246     }
247 
248     /**
249      * @throws Exception if an error occurs
250      */
251     @Test
252     @Alerts({" ",
253              " ",
254              "false",
255              "false-false-true-false-false-false-false-false-false-false-false",
256              "true",
257              "§§URL§§", "1"})
258     public void patternValidationBlank() throws Exception {
259         validation("<input type='search' pattern='[0-9a-zA-Z]{10,40}' id='e1' value=' ' name='k'>\n", "", null);
260     }
261 
262     /**
263      * @throws Exception if an error occurs
264      */
265     @Test
266     @Alerts({"  \t",
267              "  \t",
268              "false",
269              "false-false-true-false-false-false-false-false-false-false-false",
270              "true",
271              "§§URL§§", "1"})
272     public void patternValidationWhitespace() throws Exception {
273         validation("<input type='search' pattern='[0-9a-zA-Z]{10,40}' id='e1' value='  \t' name='k'>\n", "", null);
274     }
275 
276     /**
277      * @throws Exception if an error occurs
278      */
279     @Test
280     @Alerts({" 210 ",
281              " 210 ",
282              "true",
283              "false-false-false-false-false-false-false-false-false-true-false",
284              "true",
285              "§§URL§§?k=+210+", "2"})
286     public void patternValidationTrimInitial() throws Exception {
287         validation("<input type='search' pattern='[ 012]{3,10}' id='e1' name='k' value=' 210 '>\n", "", null);
288     }
289 
290     /**
291      * @throws Exception if an error occurs
292      */
293     @Test
294     @Alerts({"null",
295              " 210 ",
296              "true",
297              "false-false-false-false-false-false-false-false-false-true-false",
298              "true",
299              "§§URL§§?k=+210+", "2"})
300     public void patternValidationTrimType() throws Exception {
301         validation("<input type='search' pattern='[ 012]{3,10}' id='e1' name='k'>\n", "", " 210 ");
302     }
303 
304     /**
305      * @throws Exception if an error occurs
306      */
307     @Test
308     @Alerts({"null",
309              "abcd",
310              "false",
311              "false-false-false-false-false-false-false-true-false-false-false",
312              "true",
313              "§§URL§§", "1"})
314     public void minLengthValidationInvalid() throws Exception {
315         validation("<input type='search' minlength='5' id='e1' name='k'>\n", "", "abcd");
316     }
317 
318     /**
319      * @throws Exception if an error occurs
320      */
321     @Test
322     @Alerts({"1234",
323              "1234",
324              "true",
325              "false-false-false-false-false-false-false-false-false-true-false",
326              "true",
327              "§§URL§§?k=1234", "2"})
328     public void minLengthValidationInvalidInitial() throws Exception {
329         validation("<input type='search' minlength='20' id='e1' name='k' value='1234'>\n", "", null);
330     }
331 
332     /**
333      * @throws Exception if an error occurs
334      */
335     @Test
336     @Alerts({"null",
337              "",
338              "true",
339              "false-false-false-false-false-false-false-false-false-true-false",
340              "true",
341              "§§URL§§?k=", "2"})
342     public void minLengthValidationInvalidNoInitial() throws Exception {
343         validation("<input type='search' minlength='5' id='e1' name='k'>\n", "", null);
344     }
345 
346     /**
347      * @throws Exception if an error occurs
348      */
349     @Test
350     @Alerts({"null",
351              "abcdefghi",
352              "true",
353              "false-false-false-false-false-false-false-false-false-true-false",
354              "true",
355              "§§URL§§?k=abcdefghi", "2"})
356     public void minLengthValidationValid() throws Exception {
357         validation("<input type='search' minlength='5' id='e1' name='k'>\n", "", "abcdefghi");
358     }
359 
360     /**
361      * @throws Exception if an error occurs
362      */
363     @Test
364     @Alerts({"null",
365              "abcd",
366              "true",
367              "false-false-false-false-false-false-false-false-false-true-false",
368              "true",
369              "§§URL§§?k=abcd", "2"})
370     public void maxLengthValidationValid() throws Exception {
371         validation("<input type='search' maxlength='5' id='e1' name='k'>\n", "", "abcd");
372     }
373 
374     /**
375      * @throws Exception if an error occurs
376      */
377     @Test
378     @Alerts({"null",
379              "abcde",
380              "true",
381              "false-false-false-false-false-false-false-false-false-true-false",
382              "true",
383              "§§URL§§?k=abcde", "2"})
384     public void maxLengthValidationInvalid() throws Exception {
385         validation("<input type='search' maxlength='5' id='e1' name='k'>\n", "", "abcdefghi");
386     }
387 
388     /**
389      * @throws Exception if an error occurs
390      */
391     @Test
392     @Alerts({"abcdefghi",
393              "abcdefghi",
394              "true",
395              "false-false-false-false-false-false-false-false-false-true-false",
396              "true",
397              "§§URL§§?k=abcdefghi", "2"})
398     public void maxLengthValidationInvalidInitial() throws Exception {
399         validation("<input type='search' maxlength='5' id='e1' value='abcdefghi' name='k'>\n", "", null);
400     }
401 
402     /**
403      * @throws Exception if an error occurs
404      */
405     @Test
406     @Alerts({"true", "false", "true", "false", "true"})
407     public void willValidate() throws Exception {
408         final String html = DOCTYPE_HTML
409                 + "<html><head>\n"
410                 + "  <script>\n"
411                 + LOG_TITLE_FUNCTION
412                 + "    function test() {\n"
413                 + "      log(document.getElementById('o1').willValidate);\n"
414                 + "      log(document.getElementById('o2').willValidate);\n"
415                 + "      log(document.getElementById('o3').willValidate);\n"
416                 + "      log(document.getElementById('o4').willValidate);\n"
417                 + "      log(document.getElementById('o5').willValidate);\n"
418                 + "    }\n"
419                 + "  </script>\n"
420                 + "</head>\n"
421                 + "<body onload='test()'>\n"
422                 + "  <form>\n"
423                 + "    <input type='search' id='o1'>\n"
424                 + "    <input type='search' id='o2' disabled>\n"
425                 + "    <input type='search' id='o3' hidden>\n"
426                 + "    <input type='search' id='o4' readonly>\n"
427                 + "    <input type='search' id='o5' style='display: none'>\n"
428                 + "  </form>\n"
429                 + "</body></html>";
430 
431         loadPageVerifyTitle2(html);
432     }
433 
434     /**
435      * @throws Exception if an error occurs
436      */
437     @Test
438     @Alerts({"null",
439              "",
440              "true",
441              "false-false-false-false-false-false-false-false-false-true-false",
442              "true",
443              "§§URL§§?k=", "2"})
444     public void validationEmpty() throws Exception {
445         validation("<input type='search' id='e1' name='k'>\n", "", null);
446     }
447 
448     /**
449      * @throws Exception if an error occurs
450      */
451     @Test
452     @Alerts({"null",
453              "",
454              "false",
455              "false-true-false-false-false-false-false-false-false-false-false",
456              "true",
457              "§§URL§§", "1"})
458     public void validationCustomValidity() throws Exception {
459         validation("<input type='search' id='e1' name='k'>\n", "elem.setCustomValidity('Invalid');", null);
460     }
461 
462     /**
463      * @throws Exception if an error occurs
464      */
465     @Test
466     @Alerts({"null",
467              "",
468              "false",
469              "false-true-false-false-false-false-false-false-false-false-false",
470              "true",
471              "§§URL§§", "1"})
472     public void validationBlankCustomValidity() throws Exception {
473         validation("<input type='search' id='e1' name='k'>\n", "elem.setCustomValidity(' ');\n", null);
474     }
475 
476     /**
477      * @throws Exception if an error occurs
478      */
479     @Test
480     @Alerts({"null",
481              "",
482              "true",
483              "false-false-false-false-false-false-false-false-false-true-false",
484              "true",
485              "§§URL§§?k=", "2"})
486     public void validationResetCustomValidity() throws Exception {
487         validation("<input type='search' id='e1' name='k'>\n",
488                 "elem.setCustomValidity('Invalid');elem.setCustomValidity('');", null);
489     }
490 
491     /**
492      * @throws Exception if an error occurs
493      */
494     @Test
495     @Alerts({"null",
496              "",
497              "false",
498              "false-false-false-false-false-false-false-false-false-false-true",
499              "true",
500              "§§URL§§", "1"})
501     public void validationRequired() throws Exception {
502         validation("<input type='search' id='e1' name='k' required>\n", "", null);
503     }
504 
505     /**
506      * @throws Exception if an error occurs
507      */
508     @Test
509     @Alerts({"null",
510              "",
511              "true",
512              "false-false-false-false-false-false-false-false-false-true-false",
513              "true",
514              "§§URL§§?k=one", "2"})
515     public void validationRequiredValueSet() throws Exception {
516         validation("<input type='search' id='e1' name='k' required>\n", "elem.value='one';", null);
517     }
518 
519     /**
520      * @throws Exception if an error occurs
521      */
522     @Test
523     @Alerts({"null",
524              "",
525              "false",
526              "false-false-true-false-false-false-false-false-false-false-false",
527              "true",
528              "§§URL§§", "1"})
529     public void validationPattern() throws Exception {
530         validation("<input type='search' id='e1' name='k' pattern='abc'>\n", "elem.value='one';", null);
531     }
532 
533     private void validation(final String htmlPart, final String jsPart, final String sendKeys) throws Exception {
534         final String html = DOCTYPE_HTML
535                 + "<html><head>\n"
536                 + "  <script>\n"
537                 + LOG_TITLE_FUNCTION
538                 + "    function logValidityState(s) {\n"
539                 + "      log(s.badInput"
540                         + "+ '-' + s.customError"
541                         + "+ '-' + s.patternMismatch"
542                         + "+ '-' + s.rangeOverflow"
543                         + "+ '-' + s.rangeUnderflow"
544                         + "+ '-' + s.stepMismatch"
545                         + "+ '-' + s.tooLong"
546                         + "+ '-' + s.tooShort"
547                         + " + '-' + s.typeMismatch"
548                         + " + '-' + s.valid"
549                         + " + '-' + s.valueMissing);\n"
550                 + "    }\n"
551                 + "    function test() {\n"
552                 + "      var elem = document.getElementById('e1');\n"
553                 + jsPart
554                 + "      log(elem.checkValidity());\n"
555                 + "      logValidityState(elem.validity);\n"
556                 + "      log(elem.willValidate);\n"
557                 + "    }\n"
558                 + "  </script>\n"
559                 + "</head>\n"
560                 + "<body>\n"
561                 + "  <form>\n"
562                 + htmlPart
563                 + "    <button id='myTest' type='button' onclick='test()'>Test</button>\n"
564                 + "    <button id='myButton' type='submit'>Submit</button>\n"
565                 + "  </form>\n"
566                 + "</body></html>";
567 
568         final String secondContent = DOCTYPE_HTML
569                 + "<html><head><title>second</title></head><body>\n"
570                 + "  <p>hello world</p>\n"
571                 + "</body></html>";
572 
573         getMockWebConnection().setResponse(URL_SECOND, secondContent);
574         expandExpectedAlertsVariables(URL_FIRST);
575 
576         final WebDriver driver = loadPage2(html, URL_FIRST);
577 
578         final WebElement foo = driver.findElement(By.id("e1"));
579         if (sendKeys != null) {
580             foo.sendKeys(sendKeys);
581         }
582         assertEquals(getExpectedAlerts()[0], "" + foo.getDomAttribute("value"));
583         assertEquals(getExpectedAlerts()[1], foo.getDomProperty("value"));
584 
585         driver.findElement(By.id("myTest")).click();
586         verifyTitle2(driver, getExpectedAlerts()[2], getExpectedAlerts()[3], getExpectedAlerts()[4]);
587 
588         driver.findElement(By.id("myButton")).click();
589         if (useRealBrowser()) {
590             Thread.sleep(400);
591         }
592         assertEquals(getExpectedAlerts()[5], getMockWebConnection().getLastWebRequest().getUrl());
593         assertEquals(Integer.parseInt(getExpectedAlerts()[6]), getMockWebConnection().getRequestCount());
594     }
595 }