001: // Copyright © 2002-2007 Canoo Engineering AG, Switzerland.
002: package com.canoo.webtest.steps.request;
003:
004: import java.io.IOException;
005: import java.util.Iterator;
006: import java.util.List;
007:
008: import org.apache.log4j.Logger;
009: import org.jaxen.JaxenException;
010: import org.xml.sax.SAXException;
011:
012: import com.canoo.webtest.engine.Context;
013: import com.canoo.webtest.engine.StepExecutionException;
014: import com.canoo.webtest.engine.StepFailedException;
015: import com.canoo.webtest.steps.AbstractBrowserAction;
016: import com.canoo.webtest.util.ConversionUtil;
017: import com.gargoylesoftware.htmlunit.ElementNotFoundException;
018: import com.gargoylesoftware.htmlunit.Page;
019: import com.gargoylesoftware.htmlunit.WebWindow;
020: import com.gargoylesoftware.htmlunit.html.BaseFrame;
021: import com.gargoylesoftware.htmlunit.html.FrameWindow;
022: import com.gargoylesoftware.htmlunit.html.HtmlElement;
023: import com.gargoylesoftware.htmlunit.html.HtmlPage;
024:
025: /**
026: * Selects a frame as the "current active" response.
027: *
028: * @author <a href="torben@tretau.net">Torben Tretau</a>
029: * @author Marc Guillemot
030: * @webtest.step category="Core"
031: * name="followFrame"
032: * description="Locates a frame by its name (e.g. <em><FRAME NAME='myFrame' SRC='myPage.jsp'></em>)
033: * and defines it as the \"current\" response for the next steps."
034: * alias="followframe"
035: */
036: public class FollowFrame extends AbstractBrowserAction {
037: private static final Logger LOG = Logger
038: .getLogger(FollowFrame.class);
039: private String fName;
040: private String fHtmlId;
041: private String fRelative;
042: private boolean fIsRelative;
043:
044: /**
045: * @param htmlId the new value.
046: * @webtest.parameter required="yes/no"
047: * description="The id of the frame to select.
048: * One of 'name' or 'htmlid' must be set."
049: */
050: public void setHtmlId(final String htmlId) {
051: fHtmlId = htmlId;
052: }
053:
054: public String getHtmlId() {
055: return fHtmlId;
056: }
057:
058: public String getName() {
059: return fName;
060: }
061:
062: /**
063: * @param newName
064: * @webtest.parameter
065: * required="yes/no"
066: * description="The name of the frame to follow, specified in the frameset.
067: * For nested frames use the name of the frame your are after, e.g. it may be the name
068: * in the nested frameset.
069: * NOTE: if nested frames bear the same name (e.g. foo inside foo), it is not specified
070: * which of the frames will be selected.
071: * It is possible to select the top window using the special name '_top'.
072: * One of 'name' or 'htmlid' must be set."
073: */
074: public void setName(final String newName) {
075: fName = newName;
076: }
077:
078: /**
079: * Indicates if the given frame name is relative to the current response (versus to the main window)
080: *
081: * @webtest.parameter
082: * required="no"
083: * default="false"
084: * description="Indicates that the frame with the given name should be searched
085: * in the current response (rather than in the top window).
086: * In the case of nested frames this allows you to follow a first frame,
087: * perform some actions and later to follow the nested frame."
088: */
089: public String getRelative() {
090: return fRelative;
091: }
092:
093: public void setRelative(final String relative) {
094: fRelative = relative;
095: }
096:
097: protected void verifyParameters() {
098: super .verifyParameters();
099: fIsRelative = ConversionUtil.convertToBoolean(fRelative, false);
100: nullResponseCheck();
101:
102: if (getHtmlId() != null && getName() != null) {
103: throw new StepExecutionException(
104: "\"name\" and \"htmlId\" can't be combined!", this );
105: } else if (getName() == null && getHtmlId() == null) {
106: throw new StepExecutionException(
107: "\"name\" or \"htmlId\" must be set!", this );
108: }
109: }
110:
111: protected Page findTarget(final Context context)
112: throws JaxenException, IOException, SAXException {
113: final HtmlPage page;
114: if (fIsRelative) {
115: page = (HtmlPage) context.getCurrentResponse();
116: } else { // start from the page in the top window
117: page = (HtmlPage) context.getCurrentResponse()
118: .getEnclosingWindow().getTopWindow()
119: .getEnclosedPage();
120: }
121:
122: final WebWindow frameWindow = getFrame(page, fName, fHtmlId);
123:
124: if (frameWindow == null) {
125: throw new StepFailedException(getStepLabel()
126: + " Frame not found with name: " + fName
127: + " available: " + page.getFrames(), this );
128: }
129: return frameWindow.getEnclosedPage();
130: }
131:
132: /**
133: * Search for the frame with the given name inside an html page and its nested frames.
134: *
135: * @param htmlPage the page to search in
136: * @param name the name of the searched frame, <code>null</code> if searched by htmlId
137: * @param htmlId the id of the searched frame, <code>null</code> if searched by name
138: * @return <code>null</code> if not found
139: */
140: static WebWindow getFrame(final HtmlPage htmlPage,
141: final String name, final String htmlId) {
142: LOG.info("Looking for frame (name: \"" + name
143: + "\", htmlId: \"" + htmlId + "\") in "
144: + htmlPage.getWebResponse().getUrl());
145:
146: if ("_top".equals(name)) {
147: LOG
148: .debug("Asked for frame with special name \"_top\", returning the top window for the current page");
149: return htmlPage.getEnclosingWindow().getTopWindow();
150: }
151:
152: final WebWindow theFrame;
153:
154: // first look at the frames directly contained in the page
155: if (htmlId != null) {
156: theFrame = getFrameById(htmlPage, htmlId);
157: } else // by name
158: {
159: theFrame = getFrameByName(htmlPage, name);
160: }
161:
162: if (theFrame != null) {
163: return theFrame;
164: }
165:
166: // if not a top level frame, perhaps is it a nested frame
167: for (final Iterator iter = htmlPage.getFrames().iterator(); iter
168: .hasNext();) {
169: final WebWindow elt = (WebWindow) iter.next();
170: if (elt.getEnclosedPage() instanceof HtmlPage) {
171: LOG.debug("looking at subframes of \"" + elt.getName()
172: + "\": " + elt);
173: final WebWindow frame = getFrame((HtmlPage) elt
174: .getEnclosedPage(), name, htmlId);
175: if (frame != null) {
176: return frame;
177: }
178: }
179: }
180:
181: LOG.debug("frame not found");
182: return null;
183: }
184:
185: /**
186: * Gets the frame with the given html id directly in the page
187: * @param htmlPage the page
188: * @param htmlId the id
189: * @return <code>null</code> if no frame found with this id
190: */
191: private static WebWindow getFrameById(final HtmlPage htmlPage,
192: final String htmlId) {
193: LOG.debug("Looking for element with id \"" + htmlId + "\"");
194: HtmlElement elt;
195: try {
196: elt = htmlPage.getHtmlElementById(htmlId);
197: if (elt instanceof BaseFrame) {
198: LOG.debug("it's the right one");
199: return ((BaseFrame) elt).getEnclosedWindow();
200: }
201: } catch (final ElementNotFoundException e) {
202: LOG.debug("No element with id \"" + htmlId + "\"");
203: }
204: return null;
205: }
206:
207: /**
208: * Gets the frame with the given name directly in the page
209: * @param htmlPage the page
210: * @param name the name
211: * @return <code>null</code> if no frame found with this id
212: */
213: private static WebWindow getFrameByName(final HtmlPage htmlPage,
214: final String name) {
215: final List subFrames = htmlPage.getFrames();
216: for (final Iterator iter = subFrames.iterator(); iter.hasNext();) {
217: final FrameWindow elt = (FrameWindow) iter.next();
218: LOG.debug("looking at frame \"" + elt.getName() + "\": "
219: + elt);
220: if (elt.getName().equals(name)) {
221: LOG.debug("it's the right one");
222: return elt;
223: }
224: }
225: return null;
226: }
227:
228: public void doExecute() throws Exception {
229: final Context context = getContext();
230: context.saveResponseAsCurrent(findTarget(context));
231: }
232:
233: /**
234: * Called by Ant to set the text nested between opening and closing tags.
235: * @param text the text to set
236: * @webtest.nested.parameter
237: * required="no"
238: * description="Alternative way to set the 'name' attribute."
239: */
240: public void addText(final String text) {
241: if (getName() == null)
242: setName(getProject().replaceProperties(text));
243: }
244: }
|