1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static org.htmlunit.WebDriverTestCase.LOG_TITLE_FUNCTION;
18 import static org.htmlunit.WebDriverTestCase.LOG_WINDOW_NAME_FUNCTION;
19
20 import java.io.IOException;
21 import java.net.URL;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.htmlunit.html.HtmlPage;
29 import org.htmlunit.javascript.background.JavaScriptJobManager;
30 import org.junit.jupiter.api.Test;
31 import org.junitpioneer.jupiter.RetryingTest;
32
33
34
35
36
37
38
39
40 public class WebClientWaitForBackgroundJobsTest extends SimpleWebTestCase {
41
42 private long startTime_;
43
44 private void startTimedTest() {
45 startTime_ = System.currentTimeMillis();
46 }
47
48 private void assertMaxTestRunTime(final long maxRunTimeMilliseconds) {
49 final long endTime = System.currentTimeMillis();
50 final long runTime = endTime - startTime_;
51 assertTrue("\nTest took too long to run and results may not be accurate. Please try again. "
52 + "\n Actual Run Time: "
53 + runTime
54 + "\n Max Run Time: "
55 + maxRunTimeMilliseconds, runTime < maxRunTimeMilliseconds);
56 }
57
58
59
60
61 @Test
62 public void dontWaitWhenUnnecessary() throws Exception {
63 final String content = DOCTYPE_HTML
64 + "<html>\n"
65 + "<head>\n"
66 + " <title>test</title>\n"
67 + " <script>\n"
68 + LOG_TITLE_FUNCTION
69 + " var threadID;\n"
70 + " function test() {\n"
71 + " threadID = setTimeout(doLog, 10000);\n"
72 + " }\n"
73 + " function doLog() {\n"
74 + " log('blah');\n"
75 + " }\n"
76 + " </script>\n"
77 + "</head>\n"
78 + "<body onload='test()'>\n"
79 + "</body>\n"
80 + "</html>";
81
82 final HtmlPage page = loadPage(content);
83 assertEquals("test", page.getTitleText());
84 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
85 assertNotNull(jobManager);
86 assertEquals(1, jobManager.getJobCount());
87
88 startTimedTest();
89 assertEquals(1, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(7000));
90 assertMaxTestRunTime(100);
91 assertEquals(1, jobManager.getJobCount());
92 assertEquals("test", page.getTitleText());
93 }
94
95
96
97
98 @RetryingTest(3)
99 public void dontWaitWhenUnnecessary_jobRemovesOtherJob() throws Exception {
100 final String content = DOCTYPE_HTML
101 + "<html>\n"
102 + "<head>\n"
103 + " <title>test</title>\n"
104 + " <script>\n"
105 + LOG_TITLE_FUNCTION
106 + " var longTimeoutID;\n"
107 + " function test() {\n"
108 + " longTimeoutID = setTimeout(doLog('late'), 10000);\n"
109 + " setTimeout(clearLongTimeout, 100);\n"
110 + " setTimeout(doLog('hello'), 300);\n"
111 + " }\n"
112 + " function clearLongTimeout() {\n"
113 + " log('clearLongTimeout');\n"
114 + " clearTimeout(longTimeoutID);\n"
115 + " }\n"
116 + " function doLog(_text) {\n"
117 + " return function doLog() {\n"
118 + " log(_text);\n"
119 + " }\n"
120 + " }\n"
121 + " </script>\n"
122 + "</head>\n"
123 + "<body onload='test()'>\n"
124 + "</body>\n"
125 + "</html>";
126
127 final HtmlPage page = loadPage(content);
128 assertEquals("test", page.getTitleText());
129 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
130 assertNotNull(jobManager);
131 assertEquals(3, jobManager.getJobCount());
132
133 startTimedTest();
134 assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(20_000));
135 assertMaxTestRunTime(400);
136 assertEquals(0, jobManager.getJobCount());
137
138 assertEquals("testclearLongTimeout§hello§", page.getTitleText());
139 }
140
141
142
143
144
145
146 @RetryingTest(3)
147 public void waitCalledDuringJobExecution() throws Exception {
148 final String html = DOCTYPE_HTML
149 + "<html>\n"
150 + "<head>\n"
151 + " <title>test</title>\n"
152 + " <script>\n"
153 + LOG_TITLE_FUNCTION
154 + " var intervalId;\n"
155 + " function test() {\n"
156 + " intervalId = setTimeout(doWork, 100);\n"
157 + " }\n"
158 + " function doWork() {\n"
159 + " clearTimeout(intervalId);\n"
160 + " // waitForBackgroundJavaScriptStartingBefore should be called when JS execution is here\n"
161 + " var request = new XMLHttpRequest();\n"
162 + " request.open('GET', 'wait', false);\n"
163 + " request.send('');\n"
164 + " log('end work');\n"
165 + " }\n"
166 + " </script>\n"
167 + "</head>\n"
168 + "<body onload='test()'>\n"
169 + "</body>\n"
170 + "</html>";
171
172 final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
173 final MockWebConnection webConnection = new MockWebConnection() {
174 @Override
175 public WebResponse getResponse(final WebRequest request) throws IOException {
176 if (request.getUrl().toExternalForm().endsWith("/wait")) {
177 threadSynchronizer.waitForState("just before waitForBackgroundJavaScriptStartingBefore");
178 threadSynchronizer.sleep(400);
179 }
180 return super.getResponse(request);
181 }
182 };
183 webConnection.setResponse(URL_FIRST, html);
184 webConnection.setDefaultResponse("");
185
186 final WebClient client = getWebClient();
187 client.setWebConnection(webConnection);
188
189 final HtmlPage page = client.getPage(URL_FIRST);
190 assertEquals("test", page.getTitleText());
191 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
192 assertNotNull(jobManager);
193 assertEquals(1, jobManager.getJobCount());
194
195 startTimedTest();
196 threadSynchronizer.setState("just before waitForBackgroundJavaScriptStartingBefore");
197 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
198 assertMaxTestRunTime(600);
199 assertEquals(0, jobManager.getJobCount());
200
201 assertEquals("testend work§", page.getTitleText());
202 }
203
204
205
206
207
208
209
210 @Test
211 public void waitWhenLastJobStartsNewOne() throws Exception {
212 final String html = DOCTYPE_HTML
213 + "<html>\n"
214 + "<head>\n"
215 + " <title>test</title>\n"
216 + " <script>\n"
217 + LOG_TITLE_FUNCTION
218 + " function test() {\n"
219 + " setTimeout(doWork1, 200);\n"
220 + " }\n"
221 + " function doWork1() {\n"
222 + " log('work1');\n"
223 + " setTimeout(doWork2, 200);\n"
224 + " }\n"
225 + " function doWork2() {\n"
226 + " log('work2');\n"
227 + " }\n"
228 + " </script>\n"
229 + "</head>\n"
230 + "<body onload='test()'>\n"
231 + "</body>\n"
232 + "</html>";
233
234 final HtmlPage page = loadPage(html);
235 assertEquals("test", page.getTitleText());
236 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
237 assertNotNull(jobManager);
238 assertEquals(1, jobManager.getJobCount());
239
240 startTimedTest();
241 assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(20_000));
242 assertMaxTestRunTime(1000);
243 assertEquals(0, jobManager.getJobCount());
244
245 assertEquals("testwork1§work2§", page.getTitleText());
246 }
247
248
249
250
251
252
253
254 @RetryingTest(3)
255 public void waitWithsubWindows() throws Exception {
256 final String html = DOCTYPE_HTML
257 + "<html>\n"
258 + "<head>\n"
259 + " <title>test</title>\n"
260 + "</head>\n"
261 + "<body>\n"
262 + "<iframe src='nested.html'></iframe>\n"
263 + "</body>\n"
264 + "</html>";
265 final String nested = DOCTYPE_HTML
266 + "<html>\n"
267 + "<head>\n"
268 + " <title>nested</title>\n"
269 + " <script>\n"
270 + LOG_WINDOW_NAME_FUNCTION
271 + " function test() {\n"
272 + " setTimeout(doWork1, 200);\n"
273 + " }\n"
274 + " function doWork1() {\n"
275 + " log('work1');\n"
276 + " }\n"
277 + " </script>\n"
278 + "</head>\n"
279 + "<body onload='test()'>\n"
280 + "</body>\n"
281 + "</html>";
282
283 final WebClient client = getWebClient();
284
285 final MockWebConnection webConnection = new MockWebConnection();
286 webConnection.setResponse(URL_FIRST, html);
287 webConnection.setDefaultResponse(nested);
288
289 client.setWebConnection(webConnection);
290
291 final HtmlPage page = client.getPage(URL_FIRST);
292 assertEquals("", page.getEnclosingWindow().getName());
293
294 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
295 assertNotNull(jobManager);
296 assertEquals(0, jobManager.getJobCount());
297
298 startTimedTest();
299 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
300 assertMaxTestRunTime(300);
301 assertEquals(0, jobManager.getJobCount());
302
303 assertEquals("work1§", page.getEnclosingWindow().getName());
304 }
305
306
307
308
309
310
311
312 @RetryingTest(3)
313 public void newJobStartedAfterWait() throws Exception {
314 final String html = DOCTYPE_HTML
315 + "<html>\n"
316 + "<head>\n"
317 + " <title>test</title>\n"
318 + " <script>\n"
319 + LOG_TITLE_FUNCTION
320 + " var request;\n"
321 + " function onReadyStateChange() {\n"
322 + " if (request.readyState == 4) {\n"
323 + " log('xhr onchange');\n"
324 + " setTimeout(doWork1, 200);\n"
325 + " }\n"
326 + " }\n"
327 + " function test() {\n"
328 + " request = new XMLHttpRequest();\n"
329 + " request.open('GET', 'wait', true);\n"
330 + " request.onreadystatechange = onReadyStateChange;\n"
331 + " // waitForBackgroundJavaScriptStartingBefore should be called when JS execution is in send()\n"
332 + " request.send('');\n"
333 + " }\n"
334 + " function doWork1() {\n"
335 + " log('work1');\n"
336 + " }\n"
337 + " </script>\n"
338 + "</head>\n"
339 + "<body onload='test()'>\n"
340 + "</body>\n"
341 + "</html>";
342
343 final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
344 final MockWebConnection webConnection = new MockWebConnection() {
345 @Override
346 public WebResponse getResponse(final WebRequest request) throws IOException {
347 if (request.getUrl().toExternalForm().endsWith("/wait")) {
348 threadSynchronizer.waitForState("just before waitForBackgroundJavaScriptStartingBefore");
349 threadSynchronizer.sleep(400);
350 }
351 return super.getResponse(request);
352 }
353 };
354 webConnection.setResponse(URL_FIRST, html);
355 webConnection.setDefaultResponse("");
356
357 final WebClient client = getWebClient();
358 client.setWebConnection(webConnection);
359
360 final HtmlPage page = client.getPage(URL_FIRST);
361 assertEquals("test", page.getTitleText());
362 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
363 assertNotNull(jobManager);
364 assertEquals(1, jobManager.getJobCount());
365
366 startTimedTest();
367 threadSynchronizer.setState("just before waitForBackgroundJavaScriptStartingBefore");
368 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
369 assertMaxTestRunTime(1000);
370 assertEquals(0, jobManager.getJobCount());
371
372 assertEquals("testxhr onchange§work1§", page.getTitleText());
373 }
374
375
376
377
378
379
380
381
382 @RetryingTest(3)
383 public void waitForJobThatIsAlreadyLate() throws Exception {
384 final String html = DOCTYPE_HTML
385 + "<html>\n"
386 + "<head>\n"
387 + " <script>\n"
388 + LOG_TITLE_FUNCTION
389 + " var counter = 0;\n"
390 + " function test() {\n"
391 + " setTimeout(doWork1, 0);\n"
392 + " }\n"
393 + " function doWork1() {\n"
394 + " if (counter++ < 50) {\n"
395 + " setTimeout(doWork1, 0);\n"
396 + " }\n"
397 + " log('work' + counter);\n"
398 + " }\n"
399 + " </script>\n"
400 + "</head>\n"
401 + "<body onload='test()'>\n"
402 + "</body>\n"
403 + "</html>";
404
405 final MockWebConnection webConnection = new MockWebConnection();
406 webConnection.setResponse(URL_FIRST, html);
407 webConnection.setDefaultResponse("");
408
409 final WebClient client = getWebClient();
410 client.setWebConnection(webConnection);
411
412 final HtmlPage page = client.getPage(URL_FIRST);
413 assertEquals("", page.getTitleText());
414
415 startTimedTest();
416 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(1_000));
417 assertMaxTestRunTime(1000);
418 assertTrue(page.getTitleText(), page.getTitleText().endsWith("work48§work49§work50§work51§"));
419 }
420
421
422
423
424
425 @Test
426 public void jobSchedulesJobInOtherWindow1() throws Exception {
427 final String html = DOCTYPE_HTML
428 + "<html>\n"
429 + "<head>\n"
430 + " <script>\n"
431 + " var counter = 0;\n"
432 + " function test() {\n"
433 + " var w = window.open('about:blank');\n"
434 + " w.setTimeout(doWork1, 200);\n"
435 + " }\n"
436 + " function doWork1() {\n"
437 + " alert('work1');\n"
438 + " setTimeout(doWork2, 400);\n"
439 + " }\n"
440 + " function doWork2() {\n"
441 + " alert('work2');\n"
442 + " }\n"
443 + " </script>\n"
444 + "</head>\n"
445 + "<body onload='test()'>\n"
446 + "</body>\n"
447 + "</html>";
448
449 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
450 final HtmlPage page = loadPage(html, collectedAlerts);
451
452 startTimedTest();
453 assertEquals(0, page.getWebClient().waitForBackgroundJavaScript(1000));
454 assertMaxTestRunTime(1000);
455
456 final String[] expectedAlerts = {"work1", "work2"};
457 assertEquals(expectedAlerts, collectedAlerts);
458 }
459
460
461
462
463
464 @Test
465 public void jobSchedulesJobInOtherWindow2() throws Exception {
466 final String html = DOCTYPE_HTML
467 + "<html>\n"
468 + "<head>\n"
469 + " <script>\n"
470 + LOG_TITLE_FUNCTION
471 + " var counter = 0;\n"
472 + " function test() {\n"
473 + " var w = window.open('about:blank');\n"
474 + " w.setTimeout(doWork1, 200);\n"
475 + " }\n"
476 + " function doWork1() {\n"
477 + " log('work1');\n"
478 + " setTimeout(doWork2, 400);\n"
479 + " }\n"
480 + " function doWork2() {\n"
481 + " log('work2');\n"
482 + " }\n"
483 + " </script>\n"
484 + "</head>\n"
485 + "<body onload='test()'>\n"
486 + "</body>\n"
487 + "</html>";
488
489 final HtmlPage page = loadPage(html);
490 assertEquals("", page.getTitleText());
491
492 startTimedTest();
493 assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(1000));
494 assertMaxTestRunTime(1000);
495
496 assertEquals("work1§work2§", page.getTitleText());
497 }
498
499
500
501
502
503
504
505
506
507 @RetryingTest(3)
508 public void waitForBackgroundJavaScriptStartingBefore_hangs() throws Exception {
509 final String html = DOCTYPE_HTML
510 + "<html>\n"
511 + "<head>\n"
512 + " <title>test</title>\n"
513 + " <script>\n"
514 + LOG_TITLE_FUNCTION
515 + " var start = new Date().getTime();\n"
516 + " var id = setInterval(doWork1, 35);\n"
517 + " function stopTimer() {\n"
518 + " clearInterval(id);\n"
519 + " }\n"
520 + " function doWork1() {\n"
521 + " if (start + 8000 < new Date().getTime()) {\n"
522 + " log('failed');\n"
523 + " clearInterval(id);\n"
524 + " }\n"
525 + " }\n"
526 + " </script>\n"
527 + "</head>\n"
528 + "<body>\n"
529 + "<button onclick='stopTimer()'>stop</button>\n"
530 + "</body>\n"
531 + "</html>";
532
533 final WebClient client = getWebClient();
534
535 final MockWebConnection webConnection = new MockWebConnection();
536 webConnection.setDefaultResponse(html);
537 client.setWebConnection(webConnection);
538
539 final HtmlPage page = client.getPage(URL_FIRST);
540 assertEquals("test", page.getTitleText());
541
542 int noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
543 assertTrue(noOfJobs == 1 || noOfJobs == 2);
544 assertEquals("test", page.getTitleText());
545
546 noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
547 assertTrue(noOfJobs == 1 || noOfJobs == 2);
548 assertEquals("test", page.getTitleText());
549 }
550
551
552
553
554
555
556
557
558 @Test
559 public void jobsFromAClosedWindowShouldntBeIgnore() throws Exception {
560 final String html = DOCTYPE_HTML
561 + "<html><head><title>page 1</title></head>\n"
562 + "<body>\n"
563 + "<iframe src='iframe.html'></iframe>\n"
564 + "</body></html>";
565
566 final String iframe = DOCTYPE_HTML
567 + "<html><body>\n"
568 + "<script>\n"
569 + "setTimeout(function() { parent.location = '/page3.html'; }, 50);\n"
570 + "</script>\n"
571 + "</body></html>";
572 final String page3 = DOCTYPE_HTML
573 + "<html><body><script>\n"
574 + "parent.location = '/delayedPage4.html';\n"
575 + "</script></body></html>";
576
577 final WebClient client = getWebClient();
578
579 final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
580
581 final MockWebConnection webConnection = new MockWebConnection() {
582 @Override
583 public WebResponse getResponse(final WebRequest request) throws IOException {
584 if (request.getUrl().toExternalForm().endsWith("/delayedPage4.html")) {
585 threadSynchronizer.setState("/delayedPage4.html requested");
586 threadSynchronizer.waitForState("ready to call waitForBGJS");
587 threadSynchronizer.sleep(1000);
588 }
589 return super.getResponse(request);
590 }
591 };
592
593 webConnection.setDefaultResponse(html);
594 webConnection.setResponse(new URL(URL_FIRST, "iframe.html"), iframe);
595 webConnection.setResponse(new URL(URL_FIRST, "page3.html"), page3);
596 webConnection.setResponseAsGenericHtml(new URL(URL_FIRST, "delayedPage4.html"), "page 4");
597 client.setWebConnection(webConnection);
598
599 client.getPage(URL_FIRST);
600
601 threadSynchronizer.waitForState("/delayedPage4.html requested");
602 threadSynchronizer.setState("ready to call waitForBGJS");
603 final int noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
604 assertEquals(0, noOfJobs);
605
606 final HtmlPage page = (HtmlPage) client.getCurrentWindow().getEnclosedPage();
607 assertEquals("page 4", page.getTitleText());
608 }
609
610
611
612
613 @Test
614 public void timeoutBeforeAllStartedJobsFinished() throws Exception {
615 final String html = DOCTYPE_HTML
616 + "<html>\n"
617 + "<head>\n"
618 + "</head>\n"
619 + "<body>\n"
620 + " <script>\n"
621 + LOG_TITLE_FUNCTION
622 + " var counter = 0;\n"
623 + " setTimeout(doWork1, 100);\n"
624 + " function doWork1() {\n"
625 + " log('work' + counter++);\n"
626 + " var start = new Date();\n"
627 + " while((new Date() - start) < 400) {"
628 + " Math.sqrt(Math.sqrt(Math.pi));\n"
629 + " }\n"
630 + " setTimeout(doWork1, 100);\n"
631 + " }\n"
632 + " </script>\n"
633 + "</body>\n"
634 + "</html>";
635
636 final HtmlPage page = loadPage(html);
637 assertEquals("", page.getTitleText());
638
639 startTimedTest();
640 assertEquals(1, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(650, 700));
641 assertMaxTestRunTime(800);
642
643 assertTrue(page.getTitleText(), page.getTitleText().startsWith("work0§work1§"));
644 }
645
646
647
648
649 @Test
650 public void timeoutDirectStartedJob() throws Exception {
651 final String html = DOCTYPE_HTML
652 + "<html>\n"
653 + "<head>\n"
654 + "</head>\n"
655 + "<body>\n"
656 + " <script>\n"
657 + LOG_TITLE_FUNCTION
658 + " var counter = 0;\n"
659 + " function doWork1() {\n"
660 + " log('work' + counter++);\n"
661 + " var start = new Date();\n"
662 + " while((new Date() - start) < 2000) {"
663 + " Math.sqrt(Math.sqrt(Math.pi));\n"
664 + " }\n"
665 + " }\n"
666 + " setTimeout(doWork1, 100);\n"
667 + " </script>\n"
668 + "</body>\n"
669 + "</html>";
670
671 final HtmlPage page = loadPage(html);
672 assertEquals("", page.getTitleText());
673
674 startTimedTest();
675 assertEquals(1, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(400, 1000));
676 assertMaxTestRunTime(1100);
677
678 assertTrue(page.getTitleText(), page.getTitleText().startsWith("work0§"));
679 }
680
681
682
683
684
685 static class ThreadSynchronizer {
686 private String state_ = "initial";
687 private static final Log LOG = LogFactory.getLog(ThreadSynchronizer.class);
688
689 synchronized void setState(final String newState) {
690 state_ = newState;
691 notifyAll();
692 }
693
694
695
696
697
698 public void sleep(final long millis) {
699 try {
700 if (LOG.isDebugEnabled()) {
701 LOG.debug("Sleeping for " + millis + "ms");
702 }
703 Thread.sleep(millis);
704 }
705 catch (final InterruptedException e) {
706 throw new RuntimeException(e);
707 }
708 }
709
710 synchronized void waitForState(final String expectedState) {
711 try {
712 while (!state_.equals(expectedState)) {
713 wait();
714 }
715 }
716 catch (final InterruptedException e) {
717 throw new RuntimeException(e);
718 }
719 }
720 }
721 }