1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package com.gargoylesoftware.htmlunit;
16
17 import java.io.BufferedInputStream;
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.FileNotFoundException;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.ObjectOutputStream;
25 import java.io.Serializable;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.net.ConnectException;
29 import java.net.MalformedURLException;
30 import java.net.SocketException;
31 import java.net.URL;
32 import java.net.UnknownHostException;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.ListIterator;
38 import java.util.Map;
39
40 import org.apache.commons.io.FileUtils;
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.lang.SerializationUtils;
43 import org.apache.commons.lang.StringUtils;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46 import org.junit.After;
47 import org.junit.Assert;
48 import org.junit.Before;
49 import org.junit.Test;
50
51 import com.gargoylesoftware.htmlunit.html.HtmlElement;
52 import com.gargoylesoftware.htmlunit.html.HtmlPage;
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public abstract class WebTestCase {
67
68
69 private static final Log LOG = LogFactory.getLog(WebTestCase.class);
70
71
72 public static final int PORT = Integer.parseInt(System.getProperty("htmlunit.test.port", "12345"));
73
74
75 public static final URL URL_FIRST;
76
77
78 public static final URL URL_SECOND;
79
80
81
82
83
84 public static final URL URL_THIRD;
85
86
87
88
89 public static final String JAVASCRIPT_MIME_TYPE = "application/javascript";
90
91
92
93
94
95 public static final String PROPERTY_GENERATE_TESTPAGES
96 = "com.gargoylesoftware.htmlunit.WebTestCase.GenerateTestpages";
97
98
99 protected static final String LINE_SEPARATOR = System.getProperty("line.separator");
100
101 private BrowserVersion browserVersion_;
102 private WebClient webClient_;
103 private MockWebConnection mockWebConnection_;
104
105 private String[] expectedAlerts_;
106
107 private static final BrowserVersion FLAG_ALL_BROWSERS = new BrowserVersion("", "", "", 0);
108 private static final ThreadLocal<BrowserVersion> generateTest_browserVersion_ = new ThreadLocal<BrowserVersion>();
109 private String generateTest_content_;
110 private List<String> generateTest_expectedAlerts_;
111 private boolean generateTest_notYetImplemented_;
112 private String generateTest_testName_;
113 private int nbJSThreadsBeforeTest_;
114
115 static {
116 try {
117 URL_FIRST = new URL("http://localhost:" + PORT + "/");
118 URL_SECOND = new URL("http://localhost:" + PORT + "/second/");
119 URL_THIRD = new URL("http://127.0.0.1:" + PORT + "/third/");
120 }
121 catch (final MalformedURLException e) {
122
123 throw new IllegalStateException("Unable to create URL constants");
124 }
125 }
126
127
128
129
130 protected WebTestCase() {
131 generateTest_browserVersion_.remove();
132 }
133
134
135
136
137
138
139
140 public final HtmlPage loadPage(final String html) throws Exception {
141 return loadPage(html, null);
142 }
143
144
145
146
147
148
149
150
151
152 public final HtmlPage loadPage(final BrowserVersion browserVersion,
153 final String html, final List<String> collectedAlerts) throws Exception {
154 if (generateTest_browserVersion_.get() == null) {
155 generateTest_browserVersion_.set(browserVersion);
156 }
157 return loadPage(browserVersion, html, collectedAlerts, getDefaultUrl());
158 }
159
160
161
162
163
164
165
166
167
168 public final HtmlPage loadPage(final String html, final List<String> collectedAlerts) throws Exception {
169 generateTest_browserVersion_.set(FLAG_ALL_BROWSERS);
170 final BrowserVersion version = (browserVersion_ != null) ? browserVersion_ : BrowserVersion.getDefault();
171 return loadPage(version, html, collectedAlerts, getDefaultUrl());
172 }
173
174
175
176
177
178
179
180
181 protected static final HtmlPage loadUrl(final String url) throws Exception {
182 try {
183 final WebClient client = new WebClient();
184 client.setUseInsecureSSL(true);
185 return client.getPage(url);
186 }
187 catch (final ConnectException e) {
188
189 System.out.println("Connection could not be made to " + url);
190 return null;
191 }
192 catch (final SocketException e) {
193
194 System.out.println("Connection could not be made to " + url);
195 return null;
196 }
197 catch (final UnknownHostException e) {
198
199 System.out.println("Connection could not be made to " + url);
200 return null;
201 }
202 }
203
204
205
206
207
208
209
210
211
212 protected final HtmlPage loadPage(final String html, final List<String> collectedAlerts,
213 final URL url) throws Exception {
214
215 return loadPage(BrowserVersion.getDefault(), html, collectedAlerts, url);
216 }
217
218
219
220
221
222
223
224
225
226
227 protected final HtmlPage loadPage(final BrowserVersion browserVersion,
228 final String html, final List<String> collectedAlerts, final URL url) throws Exception {
229
230 if (webClient_ == null) {
231 webClient_ = new WebClient(browserVersion);
232 }
233 return loadPage(webClient_, html, collectedAlerts, url);
234 }
235
236
237
238
239
240
241
242
243
244
245 protected static final HtmlPage loadPage(final WebClient client,
246 final String html, final List<String> collectedAlerts, final URL url) throws Exception {
247
248 if (collectedAlerts != null) {
249 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
250 }
251
252 final MockWebConnection webConnection = new MockWebConnection();
253 webConnection.setDefaultResponse(html);
254 client.setWebConnection(webConnection);
255
256 return client.getPage(url);
257 }
258
259
260
261
262
263
264
265
266
267 protected final HtmlPage loadPage(final WebClient client,
268 final String html, final List<String> collectedAlerts) throws Exception {
269
270 return loadPage(client, html, collectedAlerts, getDefaultUrl());
271 }
272
273
274
275
276
277 public static void assertNull(final Object object) {
278 Assert.assertNull("Expected null but found [" + object + "]", object);
279 }
280
281
282
283
284
285
286
287 protected void assertEquals(final URL expectedUrl, final URL actualUrl) {
288 Assert.assertEquals(expectedUrl.toExternalForm(), actualUrl.toExternalForm());
289 }
290
291
292
293
294
295
296 protected void assertEquals(final Object expected, final Object actual) {
297 Assert.assertEquals(expected, actual);
298 }
299
300
301
302
303
304
305 protected void assertEquals(final int expected, final int actual) {
306 Assert.assertEquals(expected, actual);
307 }
308
309
310
311
312
313
314 protected void assertEquals(final boolean expected, final boolean actual) {
315 Assert.assertEquals(Boolean.valueOf(expected), Boolean.valueOf(actual));
316 }
317
318
319
320
321
322
323
324
325 protected void assertEquals(final String message, final URL expectedUrl, final URL actualUrl) {
326 Assert.assertEquals(message, expectedUrl.toExternalForm(), actualUrl.toExternalForm());
327 }
328
329
330
331
332
333
334 protected void assertEquals(final String expectedUrl, final URL actualUrl) {
335 Assert.assertEquals(expectedUrl, actualUrl.toExternalForm());
336 }
337
338
339
340
341
342
343
344
345
346 protected void assertEquals(final String[] expected, final List<String> actual) {
347 assertEquals(null, expected, actual);
348 }
349
350
351
352
353
354
355
356
357
358
359 protected void assertEquals(final String message, final String[] expected, final List<String> actual) {
360 Assert.assertEquals(message, Arrays.asList(expected).toString(), actual.toString());
361 }
362
363
364
365
366
367
368
369 protected void assertEquals(final String message, final String expectedUrl, final URL actualUrl) {
370 Assert.assertEquals(message, expectedUrl, actualUrl.toExternalForm());
371 }
372
373
374
375
376
377 protected void assertTrue(final boolean condition) {
378 Assert.assertTrue(condition);
379 }
380
381
382
383
384
385
386 protected void assertTrue(final String message, final boolean condition) {
387 Assert.assertTrue(message, condition);
388 }
389
390
391
392
393
394 protected void assertFalse(final boolean condition) {
395 Assert.assertFalse(condition);
396 }
397
398
399
400
401
402
403
404
405 public static InputStream getFileAsStream(final String fileName) throws FileNotFoundException {
406 return new BufferedInputStream(new FileInputStream(getFileObject(fileName)));
407 }
408
409
410
411
412
413
414
415
416
417
418 public static File getFileObject(final String fileName) throws FileNotFoundException {
419 final String localizedName = fileName.replace('/', File.separatorChar);
420
421 File file = new File(localizedName);
422 if (!file.exists()) {
423 file = new File("../../" + localizedName);
424 }
425
426 if (!file.exists()) {
427 try {
428 System.out.println("currentDir=" + new File(".").getCanonicalPath());
429 }
430 catch (final IOException e) {
431 e.printStackTrace();
432 }
433 throw new FileNotFoundException(localizedName);
434 }
435 return file;
436 }
437
438
439
440
441
442
443
444
445 protected void createTestPageForRealBrowserIfNeeded(final String content, final String[] expectedAlerts)
446 throws IOException {
447 createTestPageForRealBrowserIfNeeded(content, Arrays.asList(expectedAlerts));
448 }
449
450
451
452
453
454
455
456
457 protected void createTestPageForRealBrowserIfNeeded(final String content, final List<String> expectedAlerts)
458 throws IOException {
459
460
461 generateTest_content_ = content;
462 generateTest_expectedAlerts_ = expectedAlerts;
463 final Method testMethod = findRunningJUnitTestMethod();
464 generateTest_testName_ = testMethod.getDeclaringClass().getSimpleName() + "_" + testMethod.getName() + ".html";
465
466 if (System.getProperty(PROPERTY_GENERATE_TESTPAGES) != null) {
467
468
469
470 String newContent = StringUtils.replace(content, "alert(", "htmlunitReserved_caughtAlert(");
471
472 final String instrumentationJS = createInstrumentationScript(expectedAlerts);
473
474
475 if (newContent.indexOf("<head>") > -1) {
476 newContent = StringUtils.replaceOnce(newContent, "<head>", "<head>" + instrumentationJS);
477 }
478 else {
479 newContent = StringUtils.replaceOnce(newContent, "<html>",
480 "<html>\n<head>\n" + instrumentationJS + "\n</head>\n");
481 }
482 final String endScript = "\n<script>htmlunitReserved_addSummaryAfterOnload();</script>\n";
483 if (newContent.contains("</body>")) {
484 newContent = StringUtils.replaceOnce(newContent, "</body>", endScript + "</body>");
485 }
486 else {
487 LOG.info("No test generated: currently only content with a <head> and a </body> is supported");
488 }
489
490 final File f = File.createTempFile("TEST" + '_', ".html");
491 FileUtils.writeStringToFile(f, newContent, "ISO-8859-1");
492 LOG.info("Test file written: " + f.getAbsolutePath());
493 }
494 else {
495 if (LOG.isDebugEnabled()) {
496 LOG.debug("System property \"" + PROPERTY_GENERATE_TESTPAGES
497 + "\" not set, don't generate test HTML page for real browser");
498 }
499 }
500 }
501
502
503
504
505
506
507 private String createInstrumentationScript(final List<String> expectedAlerts) throws IOException {
508
509 final InputStream is = getClass().getClassLoader().getResourceAsStream("alertVerifier.js");
510 final String baseJS = IOUtils.toString(is);
511 IOUtils.closeQuietly(is);
512
513 final StringBuilder sb = new StringBuilder();
514 sb.append("\n<script type='text/javascript'>\n");
515 sb.append("var htmlunitReserved_tab = [");
516 for (final ListIterator<String> iter = expectedAlerts.listIterator(); iter.hasNext();) {
517 if (iter.hasPrevious()) {
518 sb.append(", ");
519 }
520 String message = iter.next();
521 message = StringUtils.replace(message, "\\", "\\\\");
522 message = message.replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r");
523 sb.append("{expected: \"").append(message).append("\"}");
524 }
525 sb.append("];\n\n");
526 sb.append(baseJS);
527 sb.append("</script>\n");
528 return sb.toString();
529 }
530
531
532
533
534
535
536
537 protected static final MockWebConnection getMockConnection(final HtmlPage page) {
538 return (MockWebConnection) page.getWebClient().getWebConnection();
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557 protected boolean notYetImplemented() {
558 generateTest_notYetImplemented_ = true;
559 if (notYetImplementedFlag.get() != null) {
560 return false;
561 }
562 notYetImplementedFlag.set(Boolean.TRUE);
563
564 final Method testMethod = findRunningJUnitTestMethod();
565 try {
566 LOG.info("Running " + testMethod.getName() + " as not yet implemented");
567 testMethod.invoke(this, (Object[]) new Class[] {});
568 Assert.fail(testMethod.getName() + " is marked as not implemented but already works");
569 }
570 catch (final Exception e) {
571 LOG.info(testMethod.getName() + " fails which is normal as it is not yet implemented");
572
573 }
574 finally {
575 notYetImplementedFlag.set(null);
576 }
577
578 return true;
579 }
580
581
582
583
584
585
586 private Method findRunningJUnitTestMethod() {
587 final Class< ? > cl = getClass();
588 final Class< ? >[] args = new Class[] {};
589
590
591 final Throwable t = new Exception();
592 for (int i = t.getStackTrace().length - 1; i >= 0; i--) {
593 final StackTraceElement element = t.getStackTrace()[i];
594 if (element.getClassName().equals(cl.getName())) {
595 try {
596 final Method m = cl.getMethod(element.getMethodName(), args);
597 if (isPublicTestMethod(m)) {
598 return m;
599 }
600 }
601 catch (final Exception e) {
602
603 }
604 }
605 }
606
607 throw new RuntimeException("No JUnit test case method found in call stack");
608 }
609
610
611
612
613
614
615 private boolean isPublicTestMethod(final Method method) {
616 return method.getParameterTypes().length == 0
617 && (method.getName().startsWith("test") || method.getAnnotation(Test.class) != null)
618 && method.getReturnType() == Void.TYPE
619 && Modifier.isPublic(method.getModifiers());
620 }
621
622 private static final ThreadLocal<Boolean> notYetImplementedFlag = new ThreadLocal<Boolean>();
623
624
625
626
627
628
629
630
631
632
633 protected void testHTMLFile(final String fileName) throws Exception {
634 final String resourcePath = getClass().getPackage().getName().replace('.', '/') + '/' + fileName;
635 final URL url = getClass().getClassLoader().getResource(resourcePath);
636
637 final String browserKey = getBrowserVersion().getNickname().substring(0, 2);
638
639 final WebClient client = getWebClient();
640
641 final HtmlPage page = client.getPage(url);
642 final HtmlElement want = page.getHtmlElementById(browserKey);
643
644 final HtmlElement got = page.getHtmlElementById("log");
645
646 final List<String> expected = readChildElementsText(want);
647 final List<String> actual = readChildElementsText(got);
648
649 Assert.assertEquals(expected, actual);
650 }
651
652 private List<String> readChildElementsText(final HtmlElement elt) {
653 final List<String> list = new ArrayList<String>();
654 for (final HtmlElement child : elt.getChildElements()) {
655 list.add(child.asText());
656 }
657 return list;
658 }
659
660 void setBrowserVersion(final BrowserVersion browserVersion) {
661 browserVersion_ = browserVersion;
662 }
663
664
665
666
667
668 protected WebClient createNewWebClient() {
669 return new WebClient(getBrowserVersion());
670 }
671
672
673
674
675
676 protected final WebClient getWebClient() {
677 if (webClient_ == null) {
678 webClient_ = createNewWebClient();
679 }
680 return webClient_;
681 }
682
683
684
685
686
687 protected final WebClient getWebClientWithMockWebConnection() {
688 if (webClient_ == null) {
689 webClient_ = createNewWebClient();
690 webClient_.setWebConnection(getMockWebConnection());
691 }
692 return webClient_;
693 }
694
695
696
697
698
699 protected MockWebConnection getMockWebConnection() {
700 if (mockWebConnection_ == null) {
701 mockWebConnection_ = new MockWebConnection();
702 }
703 return mockWebConnection_;
704 }
705
706
707
708
709
710 protected void setMockWebConnection(final MockWebConnection connection) {
711 mockWebConnection_ = connection;
712 }
713
714
715
716
717
718 protected final BrowserVersion getBrowserVersion() {
719 if (browserVersion_ == null) {
720 throw new IllegalStateException("You must annotate the test class with '@RunWith(BrowserRunner.class)'");
721 }
722 return browserVersion_;
723 }
724
725
726
727
728
729 protected void setExpectedAlerts(final String... expectedAlerts) {
730 expectedAlerts_ = expectedAlerts;
731 }
732
733
734
735
736
737 protected String[] getExpectedAlerts() {
738 return expectedAlerts_;
739 }
740
741
742
743
744
745
746
747
748
749 protected final HtmlPage loadPageWithAlerts(final String html) throws Exception {
750 return loadPageWithAlerts(html, getDefaultUrl(), -1);
751 }
752
753
754
755
756
757
758
759
760
761
762
763 protected final HtmlPage loadPageWithAlerts(final String html, final URL url, final int waitForJS)
764 throws Exception {
765 if (expectedAlerts_ == null) {
766 throw new IllegalStateException("You must annotate the test class with '@RunWith(BrowserRunner.class)'");
767 }
768
769
770 expandExpectedAlertsVariables(url);
771
772 createTestPageForRealBrowserIfNeeded(html, expectedAlerts_);
773
774 final WebClient client = getWebClientWithMockWebConnection();
775 final List<String> collectedAlerts = new ArrayList<String>();
776 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
777
778 final MockWebConnection webConnection = getMockWebConnection();
779 webConnection.setResponse(url, html);
780
781 final HtmlPage page = client.getPage(url);
782 if (waitForJS > 0) {
783 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(waitForJS));
784 }
785 assertEquals(expectedAlerts_, collectedAlerts);
786 return page;
787 }
788
789
790
791
792
793 protected void expandExpectedAlertsVariables(final URL url) {
794 if (expectedAlerts_ == null) {
795 throw new IllegalStateException("You must annotate the test class with '@RunWith(BrowserRunner.class)'");
796 }
797 for (int i = 0; i < expectedAlerts_.length; ++i) {
798 expectedAlerts_[i] = expectedAlerts_[i].replaceAll("§§URL§§", url.toExternalForm());
799 }
800 }
801
802
803
804
805
806
807
808 @SuppressWarnings("unchecked")
809 protected <T extends Serializable> T clone(final T object) {
810 return (T) SerializationUtils.clone(object);
811 }
812
813
814
815
816
817 protected static URL getDefaultUrl() {
818 return URL_FIRST;
819 }
820
821
822
823
824
825 @After
826 public void generateTestForWebDriver() throws IOException {
827 if (generateTest_content_ != null && !generateTest_notYetImplemented_) {
828 final File targetDir = new File("target/generated_tests");
829 targetDir.mkdirs();
830
831 final File outFile = new File(targetDir, generateTest_testName_);
832
833
834
835 final String newContent = StringUtils.replace(generateTest_content_, "alert(",
836 "(function(t){var x = window.__huCatchedAlerts; x = x ? x : []; "
837 + "window.__huCatchedAlerts = x; x.push(String(t))})(");
838
839 FileUtils.writeStringToFile(outFile, newContent);
840
841
842 final String suffix;
843 BrowserVersion browser = generateTest_browserVersion_.get();
844 if (browser == null) {
845 browser = getBrowserVersion();
846 }
847 if (browser == FLAG_ALL_BROWSERS) {
848 suffix = ".expected";
849 }
850 else {
851 suffix = "." + browser.getNickname() + ".expected";
852 }
853
854 final File expectedLog = new File(outFile.getParentFile(), outFile.getName() + suffix);
855
856 final FileOutputStream fos = new FileOutputStream(expectedLog);
857 final ObjectOutputStream oos = new ObjectOutputStream(fos);
858 oos.writeObject(generateTest_expectedAlerts_);
859 oos.close();
860 }
861 }
862
863
864
865
866
867 @Before
868 public void readJSThreadsBeforeTest() {
869 nbJSThreadsBeforeTest_ = getJavaScriptThreads().size();
870 }
871
872
873
874
875 @After
876 public void releaseResources() {
877 if (webClient_ != null) {
878 webClient_.closeAllWindows();
879 }
880 webClient_ = null;
881 mockWebConnection_ = null;
882
883 final List<Thread> jsThreads = getJavaScriptThreads();
884
885
886
887 if (jsThreads.size() > nbJSThreadsBeforeTest_) {
888 final Map<String, StackTraceElement[]> stackTraces = new HashMap<String, StackTraceElement[]>();
889 for (final Thread t : jsThreads) {
890 final StackTraceElement elts[] = t.getStackTrace();
891 if (elts != null) {
892 stackTraces.put(t.getName(), elts);
893 }
894 }
895
896 if (!stackTraces.isEmpty()) {
897 System.err.println("JS threads still running:");
898 for (final Map.Entry<String, StackTraceElement[]> entry : stackTraces.entrySet()) {
899 System.err.println("Thread: " + entry.getKey());
900 final StackTraceElement elts[] = entry.getValue();
901 for (final StackTraceElement elt : elts) {
902 System.err.println(elt);
903 }
904 }
905 throw new RuntimeException("JS threads are still running: " + jsThreads.size());
906 }
907 }
908 }
909
910
911
912
913
914 protected List<Thread> getJavaScriptThreads() {
915 final Thread[] threads = new Thread[Thread.activeCount() + 10];
916 Thread.enumerate(threads);
917 final List<Thread> jsThreads = new ArrayList<Thread>();
918 for (final Thread t : threads) {
919 if (t != null && t.getName().startsWith("JS executor for")) {
920 jsThreads.add(t);
921 }
922 }
923
924 return jsThreads;
925 }
926 }