1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static java.nio.charset.StandardCharsets.ISO_8859_1;
18 import static org.junit.jupiter.api.Assertions.fail;
19
20 import java.awt.image.BufferedImage;
21 import java.io.ByteArrayInputStream;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.Serializable;
26 import java.net.MalformedURLException;
27 import java.net.URL;
28 import java.nio.file.Files;
29 import java.nio.file.Paths;
30 import java.time.Duration;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Base64;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Objects;
37 import java.util.function.Supplier;
38
39 import javax.imageio.ImageIO;
40
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.lang3.SerializationUtils;
43 import org.htmlunit.junit.BrowserVersionClassTemplateInvocationContextProvider;
44 import org.htmlunit.junit.ErrorOutputChecker;
45 import org.htmlunit.junit.SetExpectedAlertsBeforeTestExecutionCallback;
46 import org.junit.jupiter.api.AfterAll;
47 import org.junit.jupiter.api.AfterEach;
48 import org.junit.jupiter.api.Assertions;
49 import org.junit.jupiter.api.BeforeAll;
50 import org.junit.jupiter.api.BeforeEach;
51 import org.junit.jupiter.api.ClassTemplate;
52 import org.junit.jupiter.api.MethodOrderer;
53 import org.junit.jupiter.api.TestInfo;
54 import org.junit.jupiter.api.TestMethodOrder;
55 import org.junit.jupiter.api.extension.ExtendWith;
56
57 import com.github.romankh3.image.comparison.ImageComparison;
58 import com.github.romankh3.image.comparison.ImageComparisonUtil;
59 import com.github.romankh3.image.comparison.model.ImageComparisonResult;
60 import com.github.romankh3.image.comparison.model.ImageComparisonState;
61
62
63
64
65
66
67
68
69
70
71
72
73
74 @ClassTemplate
75 @ExtendWith({BrowserVersionClassTemplateInvocationContextProvider.class,
76 SetExpectedAlertsBeforeTestExecutionCallback.class,
77 ErrorOutputChecker.class})
78 @TestMethodOrder(MethodOrderer.DisplayName.class)
79 public abstract class WebTestCase {
80
81
82 public static final String DOCTYPE_HTML = "<!DOCTYPE html>\n";
83
84
85
86
87 public TestInfo testInfo_;
88
89
90
91
92
93 private static final Locale SAVE_LOCALE = Locale.getDefault();
94
95
96
97
98 public static final int PORT = Integer.parseInt(System.getProperty("htmlunit.test.port", "22222"));
99
100
101 public static final int PORT2 = Integer.parseInt(System.getProperty("htmlunit.test.port2", "22223"));
102
103
104 public static final int PORT3 = Integer.parseInt(System.getProperty("htmlunit.test.port3", "22224"));
105
106
107 public static final int PORT_PRIMITIVE_SERVER = Integer.parseInt(
108 System.getProperty("htmlunit.test.port_primitive", "22225"));
109
110
111 public static final int PORT_PROXY_SERVER = Integer.parseInt(
112 System.getProperty("htmlunit.test.port_proxy", "22226"));
113
114
115 public static final int SOCKS_PROXY_PORT = Integer.parseInt(
116 System.getProperty("htmlunit.test.socksproxy.port", "22227"));
117
118
119 public static final String SOCKS_PROXY_HOST = System.getProperty("htmlunit.test.socksproxy.host", "localhost");
120
121
122 protected static final Duration DEFAULT_WAIT_TIME = Duration.ofSeconds(1);
123
124
125 public static final URL URL_FIRST;
126
127
128 public static final URL URL_SECOND;
129
130
131
132
133
134 public static final URL URL_THIRD;
135
136
137
138
139 public static final URL URL_CROSS_ORIGIN;
140
141
142
143
144
145 public static final URL URL_CROSS_ORIGIN2;
146
147
148
149
150 public static final URL URL_CROSS_ORIGIN_BASE;
151
152 private BrowserVersion browserVersion_;
153 private String[] expectedAlerts_;
154 private MockWebConnection mockWebConnection_;
155
156 static {
157 try {
158 URL_FIRST = new URL("http://localhost:" + PORT + "/");
159 URL_SECOND = new URL("http://localhost:" + PORT + "/second/");
160 URL_THIRD = new URL("http://127.0.0.1:" + PORT + "/third/");
161 URL_CROSS_ORIGIN = new URL("http://127.0.0.1:" + PORT2 + "/corsAllowAll");
162 URL_CROSS_ORIGIN2 = new URL("http://localhost:" + PORT3 + "/");
163 URL_CROSS_ORIGIN_BASE = new URL("http://localhost:" + PORT2 + "/");
164 }
165 catch (final MalformedURLException e) {
166
167 throw new IllegalStateException("Unable to create URL constants");
168 }
169 }
170
171
172
173
174 protected WebTestCase() {
175 }
176
177 @BeforeEach
178 void init(final TestInfo testInfo) {
179 testInfo_ = testInfo;
180 }
181
182
183
184
185
186 public static void assertNull(final Object object) {
187 Assertions.assertNull(object, "Expected null but found [" + object + "]");
188 }
189
190
191
192
193
194
195 public static void assertNull(final String message, final Object object) {
196 Assertions.assertNull(object, message);
197 }
198
199
200
201
202
203 public static void assertNotNull(final Object object) {
204 Assertions.assertNotNull(object);
205 }
206
207
208
209
210
211
212 public static void assertNotNull(final String message, final Object object) {
213 Assertions.assertNotNull(object, message);
214 }
215
216
217
218
219
220
221 public static void assertSame(final Object expected, final Object actual) {
222 Assertions.assertSame(expected, actual);
223 }
224
225
226
227
228
229
230
231 public static void assertSame(final String message, final Object expected, final Object actual) {
232 Assertions.assertSame(expected, actual, message);
233 }
234
235
236
237
238
239
240 public static void assertNotSame(final Object expected, final Object actual) {
241 Assertions.assertNotSame(expected, actual);
242 }
243
244
245
246
247
248
249
250 public static void assertNotSame(final String message, final Object expected, final Object actual) {
251 Assertions.assertNotSame(expected, actual, message);
252 }
253
254
255
256
257
258
259
260 protected static void assertEquals(final URL expectedUrl, final URL actualUrl) {
261 Assertions.assertEquals(expectedUrl.toExternalForm(), actualUrl.toExternalForm());
262 }
263
264
265
266
267
268
269 protected static void assertEquals(final Object expected, final Object actual) {
270 Assertions.assertEquals(expected, actual);
271 }
272
273
274
275
276
277
278
279 protected static void assertEquals(final String message, final Object expected, final Object actual) {
280 Assertions.assertEquals(expected, actual, message);
281 }
282
283
284
285
286
287
288 protected static void assertEquals(final int expected, final int actual) {
289 Assertions.assertEquals(expected, actual);
290 }
291
292
293
294
295
296
297 protected void assertEquals(final boolean expected, final boolean actual) {
298 Assertions.assertEquals(Boolean.valueOf(expected), Boolean.valueOf(actual));
299 }
300
301
302
303
304
305
306
307
308 protected void assertEquals(final String message, final URL expectedUrl, final URL actualUrl) {
309 Assertions.assertEquals(expectedUrl.toExternalForm(), actualUrl.toExternalForm(), message);
310 }
311
312
313
314
315
316
317 protected void assertEquals(final String expectedUrl, final URL actualUrl) {
318 Assertions.assertEquals(expectedUrl, actualUrl.toExternalForm());
319 }
320
321
322
323
324
325
326
327
328
329 protected void assertEquals(final String[] expected, final List<String> actual) {
330 assertEquals(null, expected, actual);
331 }
332
333
334
335
336
337
338
339
340
341
342 protected void assertEquals(final String message, final String[] expected, final List<String> actual) {
343 Assertions.assertEquals(Arrays.asList(expected).toString(), actual.toString(), message);
344 }
345
346
347
348
349
350
351
352 protected void assertEquals(final String message, final String expectedUrl, final URL actualUrl) {
353 Assertions.assertEquals(expectedUrl, actualUrl.toExternalForm(), message);
354 }
355
356
357
358
359
360 protected void assertTrue(final boolean condition) {
361 Assertions.assertTrue(condition);
362 }
363
364
365
366
367
368
369 protected void assertTrue(final String message, final boolean condition) {
370 Assertions.assertTrue(condition, message);
371 }
372
373
374
375
376
377 protected void assertFalse(final boolean condition) {
378 Assertions.assertFalse(condition);
379 }
380
381
382
383
384
385
386 protected void assertFalse(final String message, final boolean condition) {
387 Assertions.assertFalse(condition, message);
388 }
389
390
391
392
393
394 public void setBrowserVersion(final BrowserVersion browserVersion) {
395 browserVersion_ = browserVersion;
396 }
397
398
399
400
401
402 public final BrowserVersion getBrowserVersion() {
403 if (browserVersion_ == null) {
404 throw new IllegalStateException("You must annotate the test class with '@RunWith(BrowserRunner.class)'");
405 }
406 return browserVersion_;
407 }
408
409
410
411
412
413 public void setExpectedAlerts(final String... expectedAlerts) {
414 expectedAlerts_ = expectedAlerts;
415 }
416
417
418
419
420
421 protected String[] getExpectedAlerts() {
422 return expectedAlerts_;
423 }
424
425
426
427
428
429 protected void expandExpectedAlertsVariables(final URL url) {
430 expandExpectedAlertsVariables(url.toExternalForm());
431 }
432
433
434
435
436
437 protected void expandExpectedAlertsVariables(final String url) {
438 if (expectedAlerts_ == null) {
439 throw new IllegalStateException("You must annotate the test class with '@RunWith(BrowserRunner.class)'");
440 }
441 for (int i = 0; i < expectedAlerts_.length; i++) {
442 expectedAlerts_[i] = expectedAlerts_[i].replaceAll("§§URL§§", url);
443 }
444 }
445
446
447
448
449
450
451
452 protected <T extends Serializable> T clone(final T object) {
453 return SerializationUtils.clone(object);
454 }
455
456
457
458
459
460 @BeforeAll
461 public static void beforeClass() {
462 Locale.setDefault(Locale.US);
463 }
464
465
466
467
468 @AfterAll
469 public static void afterClass() {
470 Locale.setDefault(SAVE_LOCALE);
471 }
472
473
474
475
476
477
478
479 protected void verify(final Supplier<String> func, final String expected) throws Exception {
480 verify(func, expected, DEFAULT_WAIT_TIME);
481 }
482
483
484
485
486
487
488
489
490 protected void verify(final Supplier<String> func, final String expected,
491 final Duration maxWaitTime) throws Exception {
492 final long maxWait = System.currentTimeMillis() + maxWaitTime.toMillis();
493
494 String actual = null;
495 while (System.currentTimeMillis() < maxWait) {
496 actual = func.get();
497
498 if (Objects.equals(expected, actual)) {
499 break;
500 }
501
502 Thread.sleep(50);
503 }
504
505 assertEquals(expected, actual);
506 }
507
508
509
510
511
512 protected MockWebConnection getMockWebConnection() {
513 if (mockWebConnection_ == null) {
514 mockWebConnection_ = new MockWebConnection();
515 }
516 return mockWebConnection_;
517 }
518
519
520
521
522 @AfterEach
523 public void releaseResources() {
524 if (mockWebConnection_ != null) {
525 mockWebConnection_.clear();
526 }
527 }
528
529
530
531
532
533 protected List<Thread> getJavaScriptThreads() {
534 final Thread[] threads = new Thread[Thread.activeCount() + 10];
535 Thread.enumerate(threads);
536 final List<Thread> jsThreads = new ArrayList<>();
537 for (final Thread t : threads) {
538 if (t != null && t.getName().startsWith("JS executor for")) {
539 jsThreads.add(t);
540 }
541 }
542
543 return jsThreads;
544 }
545
546
547
548
549
550
551
552 protected String getFileContent(final String fileName) throws IOException {
553 final InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
554 assertNotNull(fileName, stream);
555 return IOUtils.toString(stream, ISO_8859_1);
556 }
557
558 protected void compareImages(final String expected, final String current) throws IOException {
559 final String currentBase64Image = current.split(",")[1];
560 final byte[] currentImageBytes = Base64.getDecoder().decode(currentBase64Image);
561
562 try (ByteArrayInputStream currentBis = new ByteArrayInputStream(currentImageBytes)) {
563 final BufferedImage currentImage = ImageIO.read(currentBis);
564
565 compareImages(expected, current, currentImage);
566 }
567 }
568
569 protected void compareImages(final String expected,
570 final String current, final BufferedImage currentImage) throws IOException {
571 final String expectedBase64Image = expected.split(",")[1];
572 final byte[] expectedImageBytes = Base64.getDecoder().decode(expectedBase64Image);
573
574 try (ByteArrayInputStream expectedBis = new ByteArrayInputStream(expectedImageBytes)) {
575 final BufferedImage expectedImage = ImageIO.read(expectedBis);
576
577 final ImageComparison imageComparison = new ImageComparison(expectedImage, currentImage);
578
579 imageComparison.setPixelToleranceLevel(0.2);
580 imageComparison.setAllowingPercentOfDifferentPixels(7);
581
582 final ImageComparisonResult imageComparisonResult = imageComparison.compareImages();
583 final ImageComparisonState imageComparisonState = imageComparisonResult.getImageComparisonState();
584
585 if (ImageComparisonState.SIZE_MISMATCH == imageComparisonState) {
586 final String dir = "target/" + testInfo_.getDisplayName();
587 Files.createDirectories(Paths.get(dir));
588
589 final File expectedOut = new File(dir, "expected.png");
590 final File currentOut = new File(dir, "current.png");
591 ImageComparisonUtil.saveImage(expectedOut, expectedImage);
592 ImageComparisonUtil.saveImage(currentOut, currentImage);
593
594 String fail = "The images are different in size - "
595 + "expected: " + expectedImage.getWidth() + "x" + expectedImage.getHeight()
596 + " current: " + currentImage.getWidth() + "x" + currentImage.getHeight()
597 + " (expected: " + expectedOut.getAbsolutePath()
598 + " current: " + currentOut.getAbsolutePath() + ")";
599 if (current != null) {
600 fail += "; current data: '" + current + "'";
601 }
602 fail(fail);
603 }
604 else if (ImageComparisonState.MISMATCH == imageComparisonState) {
605 final String dir = "target/" + testInfo_.getDisplayName();
606 Files.createDirectories(Paths.get(dir));
607
608 final File expectedOut = new File(dir, "expected.png");
609 final File currentOut = new File(dir, "current.png");
610 final File differenceOut = new File(dir, "difference.png");
611 ImageComparisonUtil.saveImage(expectedOut, expectedImage);
612 ImageComparisonUtil.saveImage(currentOut, currentImage);
613 ImageComparisonUtil.saveImage(differenceOut, imageComparisonResult.getResult());
614
615 String fail = "The images are different (expected: " + expectedOut.getAbsolutePath()
616 + " current: " + currentOut.getAbsolutePath()
617 + " difference: " + differenceOut.getAbsolutePath() + ")";
618 if (current != null) {
619 fail += "; current data: '" + current + "'";
620 }
621 fail(fail);
622 }
623 }
624 }
625 }