All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xceptance.xlt.api.actions.AbstractHtmlPageAction Maven / Gradle / Ivy

Go to download

XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.

There is a newer version: 8.1.0
Show newest version
/*
 * Copyright (c) 2005-2022 Xceptance Software Technologies GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xceptance.xlt.api.actions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.SubmittableElement;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.xceptance.common.util.ParameterCheckUtils;
import com.xceptance.xlt.api.engine.NetworkData;
import com.xceptance.xlt.api.engine.NetworkDataManager;
import com.xceptance.xlt.api.engine.Session;
import com.xceptance.xlt.api.util.XltException;
import com.xceptance.xlt.api.util.XltProperties;
import com.xceptance.xlt.common.XltConstants;
import com.xceptance.xlt.engine.SessionImpl;
import com.xceptance.xlt.engine.XltWebClient;
import com.xceptance.xlt.engine.resultbrowser.RequestHistory;

/**
 * AbstractHtmlPageAction is the base class for all HTML-based actions. In contrast to
 * {@link AbstractLightWeightPageAction}, the loaded page is parsed and stored internally as a tree of elements. This
 * makes it easy to locate and query/manipulate a certain page element.
 * 

* In case JavaScript processing is enabled, the loaded pages might start JS background jobs (via window.setTimeout() * and window.setInterval()). In order to wait for these jobs to finish, an optional waiting time can be specified for * each of the page loading methods. The framework will not return from these methods unless either all background jobs * are finished or the specified waiting time is exceeded. Note that in the latter case any pending (i.e. scheduled, but * not running yet) background job is removed, while any running job is left running. If no waiting time is specified, a * default waiting time is read from the configuration. *

* Note that if the specified waiting time is negative, the framework will not wait for any job to finish, nor will it * cancel pending jobs. * * @see AbstractLightWeightPageAction * @author Jörg Werner (Xceptance Software Technologies GmbH) */ public abstract class AbstractHtmlPageAction extends AbstractWebAction { /** * Waiting time property key. */ private static final String PROP_JS_BACKGROUND_ACTIVITY_WAITINGTIME = XltConstants.XLT_PACKAGE_PATH + ".js.backgroundActivity.waitingTime"; /** * Default waiting time value. */ private static final long DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME = XltProperties.getInstance() .getProperty(PROP_JS_BACKGROUND_ACTIVITY_WAITINGTIME, -1); /** * The parsed HTML page object generated by this action. */ private HtmlPage htmlPage; /** * Creates a new AbstractHtmlPageAction object and gives it the passed timer name. This constructor is typically * used for an intermediate action in a sequence of actions, i.e. it has a previous action. * * @param previousAction * the action that preceded the current action * @param timerName * the name of the timer that is associated with this action */ protected AbstractHtmlPageAction(final AbstractWebAction previousAction, final String timerName) { super(previousAction, timerName); } /** * Creates a new AbstractHtmlPageAction object and gives it the passed timer name. This constructor is typically * used for the first action in a sequence of actions, i.e. it has no previous action. * * @param timerName * the name of the timer that is associated with this action */ protected AbstractHtmlPageAction(final String timerName) { this(null, timerName); } /** * Returns the parsed HTML page object generated by this action. * * @return the page */ public HtmlPage getHtmlPage() { return htmlPage; } /** * {@inheritDoc} */ @Override public AbstractHtmlPageAction getPreviousAction() { return (AbstractHtmlPageAction) super.getPreviousAction(); } /** * {@inheritDoc} */ @Override public void run() throws Throwable { try { super.run(); } finally { /* * Dump the page not before the very end of this action. This way, all requests that are executed after one * of the loadPageByXXX() methods are correctly associated with this action. */ dumpPage(getHtmlPage()); Session.getCurrent().getNetworkDataManager().clear(); } } /** * Sets the given HTML page object to be the result of this action. Typically, the HTML page is generated by one of * the loadPage...() methods and need not be set explicitly. * * @param htmlPage * the page to set */ public void setHtmlPage(final HtmlPage htmlPage) { setHtmlPage(htmlPage, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Sets the given HTML page object to be the result of this action and waits for background task to be finished. * Typically, the HTML page is generated by one of the loadPage...() methods and need not be set * explicitly. * * @param htmlPage * the page to set * @param waitingTime * Waiting time for all background tasks on the page to be finished */ public void setHtmlPage(final HtmlPage htmlPage, final long waitingTime) { ParameterCheckUtils.isNotNull(htmlPage, "htmlPage"); this.htmlPage = waitForPageIsComplete(htmlPage, waitingTime); } /** * Loads the page using the given request method from the passed URL. The specified request parameters are added * either to the request body if the request method is {@link HttpMethod#POST} or appended to the query string of * the target URL for any other request method. * * @param url * the target URL * @param method * the HTTP request method to be used * @param requestParameters * the list of custom parameters to add * @param waitingTime * Waiting time for all background tasks on the page to be finished * @throws Exception * if an error occurred while loading the page */ protected void loadPage(final URL url, final HttpMethod method, final List requestParameters, final long waitingTime) throws Exception { final WebRequest webRequest = createWebRequestSettings(url, method, requestParameters); final Page result = getWebClient().getPage(webRequest); htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page using the given request method from the passed URL. The specified request parameters are added * either to the request body if the request method is {@link HttpMethod#POST} or appended to the query string of * the target URL for any other request method. * * @param url * the target URL * @param method * the HTTP request method to be used * @param requestParameters * the list of custom parameters to add * @throws Exception * if an error occurred while loading the page */ protected void loadPage(final URL url, final HttpMethod method, final List requestParameters) throws Exception { loadPage(url, method, requestParameters, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page using HTTP GET from the passed URL and waits for background task to be finished. * * @param url * the target URL * @param waitingTime * Waiting time for all background tasks on the page to be finished * @throws Exception * if an error occurred while loading the page */ protected void loadPage(final URL url, final long waitingTime) throws Exception { loadPage(url, HttpMethod.GET, EMPTY_PARAMETER_LIST, waitingTime); } /** * Loads the page using HTTP GET from the passed URL. * * @param url * the target URL * @throws Exception * if an error occurred while loading the page */ protected void loadPage(final URL url) throws Exception { loadPage(url, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page using HTTP GET from the passed URL. * * @param urlAsString * the target URL as string * @param waitingTime * Waiting time for all background tasks on the page to be finished * @throws Exception * if an error occurred while loading the page */ protected void loadPage(final String urlAsString, final long waitingTime) throws Exception { final URL url = new URL(urlAsString); loadPage(url, waitingTime); } /** * Loads the page using HTTP GET from the passed URL. * * @param urlAsString * the target URL as string * @throws Exception * if an error occurred while loading the page */ protected void loadPage(final String urlAsString) throws Exception { loadPage(urlAsString, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by "clicking" the passed HTML element. * * @param element * the HTML element to click * @param waitingTime * Waiting time for all background tasks on the page to be finished * @throws Exception * if an error occurred while loading the page */ protected void loadPageByClick(final HtmlElement element, final long waitingTime) throws Exception { final Page result = element.click(); htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page by "clicking" the passed HTML element. * * @param element * the HTML element to click * @throws Exception * if an error occurred while loading the page */ protected void loadPageByClick(final HtmlElement element) throws Exception { loadPageByClick(element, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by drag and drop * * @param draggable * the dragged object * @param dropTarget * the element on which to drop * @throws Exception * if an error occurred while loading the page */ protected void loadPageByDragAndDrop(final HtmlElement draggable, final HtmlElement dropTarget, final long waitingTime) throws Exception { draggable.mouseDown(); dropTarget.mouseMove(); final Page result = dropTarget.mouseUp(); htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page by drag and drop * * @param draggable * the dragged object * @param dropTarget * the element on which to drop * @throws Exception * if an error occurred while loading the page */ protected void loadPageByDragAndDrop(final HtmlElement draggable, final HtmlElement dropTarget) throws Exception { loadPageByDragAndDrop(draggable, dropTarget, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by "clicking" the passed HTML element in the specified HTML form element. This method skips hidden * elements and reports an error if a suitable element to click on was not found. * * @param form * the HTML form * @param elementToClick * the HTML element to click, cannot be a hidden element, this can be the name or the id. The name will * be tried first, after that the id. * @param randomPosition * (parameter ignored) * @param waitingTime * Waiting time for all background tasks on the page to be finished * @throws Exception * if an error occurred while loading the page * @throws ElementMissingException * if we do not have an element to click on * @deprecated As of XLT 4.9.0, use {@link #loadPageByFormClick(HtmlForm, String, long)} instead. */ @Deprecated protected void loadPageByFormClick(final HtmlForm form, final String elementToClick, final boolean randomPosition, final long waitingTime) throws Exception { loadPageByFormClick(form, elementToClick, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by "clicking" the passed HTML element in the specified HTML form element. This method skips hidden * elements and reports an error if a suitable element to click on was not found. * * @param form * the HTML form * @param elementToClick * the HTML element to click, cannot be a hidden element, this can be the name or the id. The name will * be tried first, after that the id. * @param randomPosition * (parameter ignored) * @throws Exception * if an error occurred while loading the page * @throws ElementMissingException * if we do not have an element to click on * @deprecated As of XLT 4.9.0, use {@link #loadPageByFormClick(HtmlForm, String)} instead. */ @Deprecated protected void loadPageByFormClick(final HtmlForm form, final String elementToClick, final boolean randomPosition) throws Exception { loadPageByFormClick(form, elementToClick); } /** * Loads the page by "clicking" the passed HTML element in the specified HTML form element. * * @param form * the HTML form * @param elementToClick * the HTML element to click, cannot be a hidden element, this can be the name or the id. The name will * be tried first, after that the id. * @param waitingTime * Waiting time for all background tasks on the page to be finished * @throws Exception * if an error occurred while loading the page */ protected void loadPageByFormClick(final HtmlForm form, final String elementToClick, final long waitingTime) throws Exception { final List elements = new ArrayList(); // the input elements first elements.addAll(form.getInputsByName(elementToClick)); // the form buttons second elements.addAll(form.getButtonsByName(elementToClick)); // in case we do not have at least an element by name - try the ID if (elements.isEmpty()) { elements.add(((HtmlPage) form.getPage()).getHtmlElementById(elementToClick)); } // ok, we might have more than one element with the same name, not nice // but possible // to avoid picking the wrong one, we at least skip elements that are // hidden HtmlElement element2Click = null; for (int i = 0; i < elements.size(); i++) { final HtmlElement input = elements.get(i); if (!(input instanceof HtmlHiddenInput)) { element2Click = input; break; } } // we cannot execute this load operation if (element2Click == null) { throw new ElementMissingException("No element with name '" + elementToClick + "' found that can be clicked on."); } Page result = element2Click.click(); htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page by "clicking" the passed HTML element in the specified HTML form element. * * @param form * the HTML form * @param elementToClick * the HTML element to click * @throws Exception * if an error occurred while loading the page */ protected void loadPageByFormClick(final HtmlForm form, final String elementToClick) throws Exception { loadPageByFormClick(form, elementToClick, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by selecting an option of the given HTML select element. * * @param select * the HTML select element * @param optionValue * the value of the HTML option to select */ protected void loadPageBySelect(final HtmlSelect select, final String optionValue) throws Exception { loadPageBySelect(select, optionValue, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by selecting an option from the given HTML select element. * * @param select * the HTML select * @param optionValue * the value of the HTML option to select * @param waitingTime * Waiting time for all background tasks on the page to be finished */ protected void loadPageBySelect(final HtmlSelect select, final String optionValue, final long waitingTime) throws Exception { final Page result = select.setSelectedAttribute(optionValue, true); htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page by selecting an option form the given HTML select element. * * @param select * the HTML select * @param option * the HTML option to select */ protected void loadPageBySelect(final HtmlSelect select, final HtmlOption option) { final Page result = select.setSelectedAttribute(option, true); htmlPage = waitForPageIsComplete(result, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by selecting an option from the given HTML select element. * * @param select * the HTML select * @param option * the HTML option to select * @param waitingTime * Waiting time for all background tasks on the page to be finished */ protected void loadPageBySelect(final HtmlSelect select, final HtmlOption option, final long waitingTime) throws Exception { final Page result = select.setSelectedAttribute(option, true); htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page by submitting the given form. Form submission is triggered by the given submittable element. * * @param form * the HTML form to submit * @param element * the submittable element used to trigger form submission * @param waitingTime * Waiting time for all background tasks on the page to be finished */ protected void loadPageByFormSubmit(final HtmlForm form, final SubmittableElement element, final long waitingTime) throws Exception { // Note that HtmlForm.submit() is not public any longer. // htmlPage = waitForPageIsComplete(form.submit(element), waitingTime); // HACK: Use reflection to call the method anyway. final Method submitMethod = HtmlForm.class.getDeclaredMethod("submit", SubmittableElement.class); submitMethod.setAccessible(true); try { submitMethod.invoke(form, element); final WebWindow w = getWebClient().getCurrentWindow(); // note, that processing postponed actions should normally play no role when javascript // is disabled, but HtmlUnit doesn't really care about that so this code should work well // for both cases: javascript on and javascript off getWebClient().getJavaScriptEngine().processPostponedActions(); htmlPage = waitForPageIsComplete(w.getEnclosedPage(), waitingTime); } catch (final InvocationTargetException te) { final Throwable cause = te.getCause(); if (cause instanceof Exception) { throw (Exception) cause; } throw new XltException("Failed to submit form", cause); } } /** * Loads the page by submitting the given form. Form submission is triggered by the given submittable element. * * @param form * the HTML form to submit * @param element * the submittable element used to trigger form submission */ protected void loadPageByFormSubmit(final HtmlForm form, final SubmittableElement element) throws Exception { loadPageByFormSubmit(form, element, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by submitting the given form. * * @param form * the HTML form to submit * @param waitingTime * Waiting time for all background tasks on the page to be finished */ protected void loadPageByFormSubmit(final HtmlForm form, final long waitingTime) throws Exception { loadPageByFormSubmit(form, null, waitingTime); } /** * Loads the page by submitting the given form. * * @param form * the HTML form to submit */ protected void loadPageByFormSubmit(final HtmlForm form) throws Exception { loadPageByFormSubmit(form, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Loads the page by typing keys. Whether or not the page is reloaded depends on the key event handlers attached to * the target HTML element. * * @param element * the target HTML element * @param text * the text to type * @param waitingTime * Waiting time for all background tasks on the page to be finished */ protected void loadPageByTypingKeys(final HtmlElement element, final String text, final long waitingTime) throws Exception { Page result = element.getPage(); for (final char ch : text.toCharArray()) { result = element.type(ch); } htmlPage = waitForPageIsComplete(result, waitingTime); } /** * Loads the page by typing keys. Whether or not the page is reloaded depends on the key event handlers attached to * the target HTML element. * * @param element * the target HTML element * @param text * the text to type */ protected void loadPageByTypingKeys(final HtmlElement element, final String text) throws Exception { loadPageByTypingKeys(element, text, DEFAULT_JS_BACKGROUND_ACTIVITY_WAITINGTIME); } /** * Waits at most for the given waiting time to let any background JavaScript activity on the specified page * complete. The resulting page is scanned for any new references to static content, which will be downloaded as * well. * * @param page * the page * @param waitingTime * Waiting time for all background tasks on the page to be finished * @return the possibly modified page */ private HtmlPage waitForPageIsComplete(final Page page, final long waitingTime) { // wait for any JavaScript background thread to finish if (page instanceof SgmlPage) { final XltWebClient webClient = (XltWebClient) ((SgmlPage) page).getWebClient(); webClient.waitForBackgroundThreads(page.getEnclosingWindow().getTopWindow().getEnclosedPage(), waitingTime); } // something might have changed, including a reload via location Page enclosedPage = page.getEnclosingWindow().getTopWindow().getEnclosedPage(); final HtmlPage newHtmlPage; // check whether the server indeed returned HTML content if (enclosedPage instanceof HtmlPage) { // yes newHtmlPage = (HtmlPage) enclosedPage; // check for any new static content to load ((XltWebClient) newHtmlPage.getWebClient()).loadNewStaticContent(newHtmlPage); } else { // no, the server returned unexpected content (e.g. plain text in a 404 message) throw new XltException("The server response could not be parsed as HTML."); } // Feature #471: API: Make the network data available for validation collectAndSetNetworkData(); return newHtmlPage; } /** * Dumps the given HTML page object to the request history. * * @param htmlPage * the page to dump */ private void dumpPage(final HtmlPage htmlPage) { final String timerName = ((XltWebClient) getWebClient()).getTimerName(); final RequestHistory requestHistory = SessionImpl.getCurrent().getRequestHistory(); if (htmlPage != null) { requestHistory.add(timerName, htmlPage); } else { // dump at least an empty page to let the action appear in the result browser requestHistory.add(timerName); } } // // === Feature #471: API: Make the network data available for validation === // /** * Network data set. */ private List netStats = null; /** * Gets the network data from the {@link NetworkDataManager} and sets the appropriate field. */ private void collectAndSetNetworkData() { netStats = Session.getCurrent().getNetworkDataManager().getData(); } /** * Returns the network data set. * * @return network data as set */ protected List getNetworkDataSet() { return netStats; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy