From 6813ded101aff25b21ecf431ad9ff43455ad7a94 Mon Sep 17 00:00:00 2001 From: tfennelly Date: Fri, 21 Aug 2015 13:29:50 +0100 Subject: [PATCH] Force the web response loading for a submit and sniff in on the response --- .../htmlunit/html/HtmlFormUtil.java | 75 ++++++++++++++----- .../org/jvnet/hudson/test/HudsonTestCase.java | 32 +++++++- .../org/jvnet/hudson/test/JenkinsRule.java | 38 ++++++++-- .../test/WebClientResponseLoadListenable.java | 61 +++++++++++++++ 4 files changed, 181 insertions(+), 25 deletions(-) create mode 100644 test/src/main/java/org/jvnet/hudson/test/WebClientResponseLoadListenable.java diff --git a/test/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlFormUtil.java b/test/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlFormUtil.java index f99e2aa732..8e971f3b9d 100644 --- a/test/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlFormUtil.java +++ b/test/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlFormUtil.java @@ -25,8 +25,13 @@ package com.gargoylesoftware.htmlunit.html; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import com.gargoylesoftware.htmlunit.Page; -import com.gargoylesoftware.htmlunit.SgmlPage; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.WebWindow; +import org.junit.Assert; +import org.jvnet.hudson.test.WebClientResponseLoadListenable; +import javax.annotation.Nonnull; import java.io.IOException; import java.util.List; @@ -35,32 +40,50 @@ import java.util.List; */ public class HtmlFormUtil { - /** - * Plain {@link com.gargoylesoftware.htmlunit.html.HtmlForm#submit()} doesn't work correctly due to the use of YUI in Hudson. - */ public static Page submit(final HtmlForm htmlForm) throws IOException { HtmlElement submitElement = getSubmitButton(htmlForm); return submit(htmlForm, submitElement); } - /** - * Plain {@link com.gargoylesoftware.htmlunit.html.HtmlForm#submit()} doesn't work correctly due to the use of YUI in Hudson. - */ public static Page submit(HtmlForm htmlForm, HtmlElement submitElement) throws IOException { - if (submitElement == null) { - return htmlForm.submit(null); - } else if (submitElement instanceof SubmittableElement) { - SgmlPage formPage = htmlForm.getPage(); - Page submitResultPage = htmlForm.submit((SubmittableElement) submitElement); - if (submitResultPage != formPage) { - return submitResultPage; + if (submitElement != null && !(submitElement instanceof SubmittableElement)) { + // Just click and return + return submitElement.click(); + } + + final HtmlPage htmlPage = (HtmlPage) htmlForm.getPage(); + final WebClient webClient = htmlPage.getWebClient(); + Page resultPage = null; + + try { + resultPage = htmlForm.submit((SubmittableElement) submitElement); + } finally { + // The HtmlForm submit doesn't really do anything. It just adds a "LoadJob" + // to an internal queue. What we are doing here is manually forcing the load of + // the response for that submit LoadJob and listening in on the WebClient + // instance for the response, allowing us to create the correct HtmlPage + // object for return to the tests. + if (webClient instanceof WebClientResponseLoadListenable) { + FormSubmitResponseLoadListener loadListener = new FormSubmitResponseLoadListener(); + + ((WebClientResponseLoadListenable) webClient).addResponseLoadListener(loadListener); + try { + webClient.loadDownloadedResponses(); + resultPage = loadListener.getPage(); + } finally { + ((WebClientResponseLoadListenable) webClient).removeResponseLoadListener(loadListener); + } + + if (resultPage == htmlPage) { + // We're still on the same page (form submit didn't bring us anywhere). + // Hackery. Seems like YUI is messing us about. + return submitElement.click(); + } } else { - // We're still on the same page (form submit didn't bring us anywhere). - // Hackery. Seems like YUI is messing us about. - return submitElement.click(); + Assert.fail("WebClient doesn't implement WebClientResponseLoadListenable."); } - } else { - return submitElement.click(); + + return resultPage; } } @@ -102,4 +125,18 @@ public class HtmlFormUtil { } throw new ElementNotFoundException("button", "caption", caption); } + + private static class FormSubmitResponseLoadListener implements WebClientResponseLoadListenable.WebClientResponseLoadListener { + private WebWindow webWindow; + @Override + public void onLoad(@Nonnull WebResponse webResponse, @Nonnull WebWindow webWindow) { + this.webWindow = webWindow; + } + private Page getPage() { + if (webWindow == null) { + Assert.fail("Expected FormSubmitResponseLoadListener to be called on form submit."); + } + return webWindow.getEnclosedPage(); + } + } } diff --git a/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java b/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java index 8717c27704..ec2a7daa20 100644 --- a/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java +++ b/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java @@ -26,6 +26,8 @@ package org.jvnet.hudson.test; import com.gargoylesoftware.htmlunit.AlertHandler; import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.WebWindow; import com.gargoylesoftware.htmlunit.html.DomNodeUtil; import com.gargoylesoftware.htmlunit.html.HtmlFormUtil; import com.gargoylesoftware.htmlunit.html.HtmlImage; @@ -124,6 +126,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import javax.annotation.Nonnull; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; @@ -1622,9 +1625,11 @@ public abstract class HudsonTestCase extends TestCase implements RootAction { * Extends {@link com.gargoylesoftware.htmlunit.WebClient} and provide convenience methods * for accessing Hudson. */ - public class WebClient extends com.gargoylesoftware.htmlunit.WebClient { + public class WebClient extends com.gargoylesoftware.htmlunit.WebClient implements WebClientResponseLoadListenable { private static final long serialVersionUID = 5808915989048338267L; + private List loadListeners = new CopyOnWriteArrayList<>(); + public WebClient() { // default is IE6, but this causes 'n.doScroll('left')' to fail in event-debug.js:1907 as HtmlUnit doesn't implement such a method, // so trying something else, until we discover another problem. @@ -1686,6 +1691,31 @@ public abstract class HudsonTestCase extends TestCase implements RootAction { //setTimeout(60*1000); } + @Override + public void addResponseLoadListener(@Nonnull WebClientResponseLoadListener listener) { + removeResponseLoadListener(listener); + loadListeners.add(listener); + } + + @Override + public void removeResponseLoadListener(@Nonnull WebClientResponseLoadListener listener) { + loadListeners.remove(listener); + } + + @Override + public Page loadWebResponseInto(WebResponse webResponse, WebWindow webWindow) throws IOException, FailingHttpStatusCodeException { + try { + return super.loadWebResponseInto(webResponse, webWindow); + } finally { + if (!loadListeners.isEmpty()) { + for (WebClientResponseLoadListener listener : loadListeners) { + listener.onLoad(webResponse, webWindow); + } + } + } + } + + /** * Logs in to Jenkins. */ diff --git a/test/src/main/java/org/jvnet/hudson/test/JenkinsRule.java b/test/src/main/java/org/jvnet/hudson/test/JenkinsRule.java index 20f4b87e40..a83c32e4c5 100644 --- a/test/src/main/java/org/jvnet/hudson/test/JenkinsRule.java +++ b/test/src/main/java/org/jvnet/hudson/test/JenkinsRule.java @@ -32,6 +32,7 @@ import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.WebWindow; import com.gargoylesoftware.htmlunit.html.DomNode; import com.gargoylesoftware.htmlunit.html.HtmlButton; import com.gargoylesoftware.htmlunit.html.HtmlElement; @@ -39,7 +40,6 @@ import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlImage; import com.gargoylesoftware.htmlunit.html.HtmlInput; import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; import com.gargoylesoftware.htmlunit.javascript.HtmlUnitContextFactory; import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest; import com.gargoylesoftware.htmlunit.util.NameValuePair; @@ -147,6 +147,7 @@ import java.util.TimerTask; import java.util.TreeSet; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; @@ -159,6 +160,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; @@ -1828,9 +1830,11 @@ public class JenkinsRule implements TestRule, MethodRule, RootAction { * Extends {@link com.gargoylesoftware.htmlunit.WebClient} and provide convenience methods * for accessing Hudson. */ - public class WebClient extends com.gargoylesoftware.htmlunit.WebClient { + public class WebClient extends com.gargoylesoftware.htmlunit.WebClient implements WebClientResponseLoadListenable { private static final long serialVersionUID = 5808915989048338267L; + private List loadListeners = new CopyOnWriteArrayList<>(); + public WebClient() { // default is IE6, but this causes 'n.doScroll('left')' to fail in event-debug.js:1907 as HtmlUnit doesn't implement such a method, // so trying something else, until we discover another problem. @@ -1890,6 +1894,30 @@ public class JenkinsRule implements TestRule, MethodRule, RootAction { //setTimeout(60*1000); } + @Override + public void addResponseLoadListener(@Nonnull WebClientResponseLoadListener listener) { + removeResponseLoadListener(listener); + loadListeners.add(listener); + } + + @Override + public void removeResponseLoadListener(@Nonnull WebClientResponseLoadListener listener) { + loadListeners.remove(listener); + } + + @Override + public Page loadWebResponseInto(WebResponse webResponse, WebWindow webWindow) throws IOException, FailingHttpStatusCodeException { + try { + return super.loadWebResponseInto(webResponse, webWindow); + } finally { + if (!loadListeners.isEmpty()) { + for (WebClientResponseLoadListener listener : loadListeners) { + listener.onLoad(webResponse, webWindow); + } + } + } + } + /** * Logs in to Jenkins. */ @@ -1925,7 +1953,7 @@ public class JenkinsRule implements TestRule, MethodRule, RootAction { * and passwords. All the test accounts have the same user name and password. */ public WebClient login(String username) throws Exception { - login(username,username); + login(username, username); return this; } @@ -2008,7 +2036,7 @@ public class JenkinsRule implements TestRule, MethodRule, RootAction { } public HtmlPage getPage(Node item) throws IOException, SAXException { - return getPage(item,""); + return getPage(item, ""); } public HtmlPage getPage(Node item, String relative) throws IOException, SAXException { @@ -2127,7 +2155,7 @@ public class JenkinsRule implements TestRule, MethodRule, RootAction { NameValuePair crumb = new NameValuePair( jenkins.getCrumbIssuer().getDescriptor().getCrumbRequestField(), jenkins.getCrumbIssuer().getCrumb( null )); - req.setRequestParameters(Arrays.asList( crumb )); + req.setRequestParameters(Arrays.asList(crumb)); return req; } diff --git a/test/src/main/java/org/jvnet/hudson/test/WebClientResponseLoadListenable.java b/test/src/main/java/org/jvnet/hudson/test/WebClientResponseLoadListenable.java new file mode 100644 index 0000000000..f903123487 --- /dev/null +++ b/test/src/main/java/org/jvnet/hudson/test/WebClientResponseLoadListenable.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright (c) 2015, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.hudson.test; + +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.WebWindow; + +import javax.annotation.Nonnull; + +/** + * Hook into the HtmlUnit {@link org.jvnet.hudson.test.JenkinsRule.WebClient} + * response loading into a "window". + * @author tom.fennelly@gmail.com + */ +public interface WebClientResponseLoadListenable { + + /** + * Add a listener. + * @param listener The listener. + */ + void addResponseLoadListener(@Nonnull WebClientResponseLoadListener listener); + + /** + * Removing a listener. + * @param listener The listener. + */ + void removeResponseLoadListener(@Nonnull WebClientResponseLoadListener listener); + + /** + * Load listener interface. + */ + public interface WebClientResponseLoadListener { + /** + * Response load event. + * @param webResponse The response. + * @param webWindow The window into which the response is being loaded. + */ + void onLoad(@Nonnull WebResponse webResponse, @Nonnull WebWindow webWindow); + } +} -- GitLab