001: package com.canoo.webtest.engine;
002:
003: import java.util.ArrayList;
004: import java.util.Iterator;
005: import java.util.List;
006: import java.util.Stack;
007:
008: import org.apache.log4j.Logger;
009:
010: import com.canoo.webtest.steps.Step;
011: import com.canoo.webtest.util.Checker;
012: import com.gargoylesoftware.htmlunit.Page;
013: import com.gargoylesoftware.htmlunit.TopLevelWindow;
014: import com.gargoylesoftware.htmlunit.WebClient;
015: import com.gargoylesoftware.htmlunit.WebResponse;
016: import com.gargoylesoftware.htmlunit.WebWindow;
017: import com.gargoylesoftware.htmlunit.WebWindowEvent;
018: import com.gargoylesoftware.htmlunit.WebWindowListener;
019: import com.gargoylesoftware.htmlunit.html.FrameWindow;
020: import com.gargoylesoftware.htmlunit.html.HtmlForm;
021: import com.gargoylesoftware.htmlunit.html.HtmlPage;
022:
023: /*
024: * Contains all context information bound to a WebClient.
025: * Except if selectWebClient is used test runs with only 1 web client
026: * and only one instance of this class is used.
027: * @author Marc Guillemot
028: */
029: public class WebClientContext {
030: private static final Logger LOG = Logger
031: .getLogger(WebClientContext.class);
032: private WebClient fWebClient;
033: private String fSavedUserName;
034: private String fSavedPassword;
035: private StoredResponse fPreviousResponse = NO_STORED_RESPONSE;
036: private StoredResponse fCurrentResponse = NO_STORED_RESPONSE;
037: private static final StoredResponse NO_STORED_RESPONSE = new StoredResponse(
038: null);
039: private final WebWindowListener fWindowListener = new CurrentWindowTracker();
040: private final Stack fWindows = new Stack();
041: private HtmlForm fCurrentForm;
042:
043: /**
044: * Used to capture the state of the (current and previous) responses to be able to
045: * restore it later. Uses the Memento pattern.
046: */
047: public static final class StoredResponses {
048: private final StoredResponse fPreviousResponse;
049: private final StoredResponse fCurrentResponse;
050:
051: private StoredResponses(final WebClientContext context) {
052: fPreviousResponse = context.fPreviousResponse;
053: fCurrentResponse = context.fCurrentResponse;
054: }
055: }
056:
057: /**
058: * Keeps a Page and its stored file together.
059: */
060: static class StoredResponse {
061: private final Page fPage;
062: private String fFile;
063:
064: StoredResponse(final Page page) {
065: fPage = page;
066: }
067:
068: public Page getPage() {
069: return fPage;
070: }
071:
072: public String getFile() {
073: return fFile;
074: }
075:
076: public void setFile(final String file) {
077: fFile = file;
078: }
079: }
080:
081: /**
082: * Tracks window event to determine the "current" response
083: */
084: class CurrentWindowTracker implements WebWindowListener {
085: public void webWindowClosed(final WebWindowEvent event) {
086: fWindows.remove(event.getWebWindow());
087: // don't change currentResponse here else it causes problems with <previousResponse>
088: LOG.debug("Window closed (contains: "
089: + event.getWebWindow().getEnclosedPage()
090: .getWebResponse().getUrl() + ")");
091: }
092:
093: public void webWindowContentChanged(final WebWindowEvent event) {
094: final WebWindow window = event.getWebWindow();
095: final WebResponse webResp = event.getNewPage()
096: .getWebResponse();
097: LOG.info("Content of window changed to " + webResp.getUrl()
098: + " (" + webResp.getContentType() + ")");
099:
100: final boolean takeItAsNew;
101: if (window instanceof TopLevelWindow
102: && event.getOldPage() == null) {
103: takeItAsNew = true;
104: LOG
105: .info("Content loaded in newly opened window, its content will become current response");
106: } else if (fCurrentResponse.getPage() != null
107: && fCurrentResponse.getPage().getEnclosingWindow() == window) {
108: takeItAsNew = true;
109: LOG
110: .info("Content of current window changed, it will become current response");
111: }
112: // content loaded in an other window as the "current" one
113: // by js becomes "current" only if new top window is opened
114: else if (getWebClient().getJavaScriptEngine() == null
115: || !getWebClient().getJavaScriptEngine()
116: .isScriptRunning()) {
117: if (window instanceof FrameWindow
118: && !HtmlPage.READY_STATE_COMPLETE
119: .equals(((FrameWindow) window)
120: .getEnclosingPage()
121: .getDocumentHtmlElement()
122: .getReadyState())) {
123: LOG
124: .info("Content of frame window has changed without javascript while enclosing page is loading, "
125: + "it will NOT become current response");
126: LOG.debug("Enclosing page's state: "
127: + ((FrameWindow) window).getEnclosingPage()
128: .getDocumentHtmlElement()
129: .getReadyState());
130: LOG.debug("Enclosing page's url: "
131: + ((FrameWindow) window).getEnclosingPage()
132: .getWebResponse().getUrl());
133: takeItAsNew = false;
134: } else {
135: LOG
136: .info("Content of window changed without javascript, it will become current response");
137: takeItAsNew = true;
138: }
139: } else {
140: LOG
141: .info("Content of window changed with javascript, it will NOT become current response");
142: takeItAsNew = false;
143: }
144:
145: if (takeItAsNew) {
146: saveResponseAsCurrent(window.getEnclosedPage());
147: }
148: }
149:
150: /**
151: * @see com.gargoylesoftware.htmlunit.WebWindowListener#webWindowOpened
152: */
153: public void webWindowOpened(final WebWindowEvent event) {
154: fWindows.push(event.getWebWindow());
155: // page is not loaded yet, don't set it now as current window
156: }
157: }
158:
159: public WebClient getWebClient() {
160: return fWebClient;
161: }
162:
163: public String getSavedUserName() {
164: return fSavedUserName;
165: }
166:
167: public String getSavedPassword() {
168: return fSavedPassword;
169: }
170:
171: /**
172: * Gets the current responses (currentResponse and previousResponse) of
173: * the context to be able to restore them later.
174: * <p/>
175: * MG: may probably be problematic (like previousResponse) when windows have been closed
176: *
177: * @return the status
178: */
179: public StoredResponses getResponses() {
180: return new StoredResponses(this );
181: }
182:
183: /**
184: * Restore the responses to a previously saved value.
185: *
186: * @param savedResponses the responses to restore
187: */
188: public void restoreResponses(final StoredResponses savedResponses) {
189: fPreviousResponse = savedResponses.fPreviousResponse;
190: fCurrentResponse = savedResponses.fCurrentResponse;
191: LOG.info("Responses restored");
192: }
193:
194: /**
195: * Gets the response on which actions and verifications will occur.
196: *
197: * @return the response
198: */
199: public Page getCurrentResponse() {
200: // test if window of current response has not been closed
201: if (fCurrentResponse.getPage() != null
202: && !fWebClient.getWebWindows()
203: .contains(
204: fCurrentResponse.getPage()
205: .getEnclosingWindow())) {
206: LOG
207: .info("The window containing current response has been closed, "
208: + "the content of the last opened window will become the current response");
209: final WebWindow window = (WebWindow) fWindows.peek();
210: saveResponseAsCurrent(window.getEnclosedPage());
211: }
212: return fCurrentResponse.getPage();
213: }
214:
215: public String getCurrentResponseFile() {
216: return fCurrentResponse.getFile();
217: }
218:
219: public void setCurrentResponseFile(final String name) {
220: fCurrentResponse.setFile(name);
221: }
222:
223: /**
224: * Gets the current response as {@link com.gargoylesoftware.htmlunit.html.HtmlPage}
225: *
226: * @param step
227: * @throws StepExecutionException if the current response isn't an html page
228: */
229: public HtmlPage getCurrentHtmlResponse(final Step step) {
230: if (!(getCurrentResponse() instanceof HtmlPage)) {
231: throw new StepExecutionException(
232: "Current response is not an HTML page but of type "
233: + getCurrentResponse().getWebResponse()
234: .getContentType(), step);
235: }
236: return (HtmlPage) getCurrentResponse();
237: }
238:
239: public void restorePreviousResponse() {
240: final WebWindow window = fPreviousResponse.getPage()
241: .getEnclosingWindow();
242: if (!fWebClient.getWebWindows().contains(window)) {
243: // register the window "back" to the browser
244: fWebClient.registerWebWindow(window);
245: }
246: saveResponseAsCurrent(fPreviousResponse);
247: }
248:
249: /**
250: * Sets the current and previous response for this context and this step.
251: *
252: * @param page The page to become the current response.
253: */
254: public void saveResponseAsCurrent(final Page page) {
255: saveResponseAsCurrent(new StoredResponse(page));
256: }
257:
258: /**
259: * Sets the current and previous response for this context and this step
260: * with the associated files (if any)
261: *
262: * @param current The future current response.
263: */
264: protected void saveResponseAsCurrent(final StoredResponse current) {
265: Checker.assertFalse(current == null
266: || current.getPage() == null,
267: "Illegal new current response");
268: setCurrentForm(null); // reset current form
269: fPreviousResponse = fCurrentResponse;
270: fCurrentResponse = current;
271:
272: LOG.info("Current response now: "
273: + current.getPage().getWebResponse().getUrl());
274: LOG
275: .debug("Previous response: "
276: + (fPreviousResponse.getPage() != null ? fPreviousResponse
277: .getPage().getWebResponse().getUrl()
278: : null));
279: }
280:
281: public void setWebClient(final WebClient webClient) {
282: fWebClient = webClient;
283: restoreWindowListener();
284: fWindows.push(webClient.getCurrentWindow());
285: }
286:
287: public void suspendWindowListener() {
288: fWebClient.removeWebWindowListener(fWindowListener);
289: }
290:
291: public void restoreWindowListener() {
292: fWebClient.addWebWindowListener(fWindowListener);
293: }
294:
295: public void setSavedUserName(final String userName) {
296: fSavedUserName = userName;
297: }
298:
299: public void setSavedPassword(final String password) {
300: fSavedPassword = password;
301: }
302:
303: /**
304: * Gets the current form in the current response. This is the one that has been selected by setting the last form field.
305: *
306: * @return <code>null</code> if no current form available
307: */
308: public HtmlForm getCurrentForm() {
309: return fCurrentForm;
310: }
311:
312: /**
313: * Sets the form that has to be used as the default one for setting fields.
314: *
315: * @param form new current form or if null then set current form to none (reset)
316: */
317: public void setCurrentForm(final HtmlForm form) {
318: fCurrentForm = form;
319: if (form != null) {
320: LOG.info("Current form set to (action="
321: + form.getActionAttribute() + ")");
322: } else {
323: LOG.info("Current form set to none");
324: }
325: }
326:
327: /**
328: * Closes the WebClient. This ensures that running js scripts are stopped.
329: * This instance should be used once this method has been called.
330: */
331: public void destroy() {
332: suspendWindowListener();
333: // first get the top windows and then close them to avoid ConcurrentModificationException
334: final List topWindows = new ArrayList();
335: for (final Iterator iter = fWebClient.getWebWindows()
336: .iterator(); iter.hasNext();) {
337: final WebWindow window = (WebWindow) iter.next();
338: if (window instanceof TopLevelWindow) {
339: topWindows.add(window);
340: }
341: }
342: for (final Iterator iter = topWindows.iterator(); iter
343: .hasNext();) {
344: final TopLevelWindow window = (TopLevelWindow) iter.next();
345: window.close();
346: }
347:
348: fWebClient = null;
349: fPreviousResponse = null;
350: fCurrentResponse = null;
351: fWindows.empty();
352: fCurrentForm = null;
353: }
354: }
|