com.spike.tg4w.htmlunit.HtmlUnitInterpreter.java Source code

Java tutorial

Introduction

Here is the source code for com.spike.tg4w.htmlunit.HtmlUnitInterpreter.java

Source

/**
 * Copyright(c) 2004-2005, SpikeSource Inc. All Rights Reserved.
 * Licensed under the Open Software License version 2.1
 * (See http://www.spikesource.com/license/oslicense.html)
 *
 * author: Vinay Srini (vsrini@spikesource.com)
 */
package com.spike.tg4w.htmlunit;

import com.spike.tg4w.common.util.*;
import com.spike.tg4w.common.file.*;
import com.spike.tg4w.common.xpath.*;
import java.io.*;
import java.util.*;
import javax.net.ssl.*;

import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.html.*;
import com.gargoylesoftware.htmlunit.xml.*;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;

import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;

public class HtmlUnitInterpreter {
    public static final String MAIN_WINDOW_NAME = "__tg4w_window__";

    List testFiles = new LinkedList();
    InterpreterConfig config = null;
    TestResult logger = null;

    public HtmlUnitInterpreter(InterpreterConfig config) {
        this.config = config;
        if (this.config.logger == null) {
            try {
                String filename = this.config.reportFile;
                if (!(new File(filename)).isAbsolute() && this.config.debugDir != null) {
                    filename = this.config.debugDir + File.separator + filename;
                }
                this.config.logger = new XmlTestResultImpl(filename, null);
                this.config.logger.setDebugLevel(this.config.debugLevel);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        this.logger = this.config.logger;
    }

    public TestResult getTestResult() {
        return this.logger;
    }

    public void addTestFile(String path) {
        File file = new File(path);
        if (file.exists()) {
            testFiles.add(file);
        } else {
            this.logger.warning("File does not exist. Skipping " + path);
        }
    }

    public void runAll() throws InterpreterException {
        try {
            for (int testCount = 0; testCount < testFiles.size(); testCount++) {
                File testFile = (File) testFiles.get(testCount);
                this.logger.testStart(testFile.getAbsolutePath());
                try {
                    System.out.println("Test #" + (testCount + 1) + "/" + testFiles.size() + " : " + testFile);
                    this.runTest(testFile);
                } catch (InterpreterException e) {
                    logger.fatal("error running: " + testFile.getAbsolutePath(), e);
                    e.printStackTrace();
                    if (e.getContext() != null) {
                        ByteArrayOutputStream stream = new ByteArrayOutputStream();
                        PrintStream pstream = new PrintStream(stream);
                        e.getContext().dump(pstream);
                        String contextDump = stream.toString();
                        logger.fatal(contextDump);
                    } else {
                        logger.error("*** ERROR Context is NULL ***");
                    }
                    if (this.config.stopOnError) {
                        logger.info("Stopping tests");
                        throw e;
                    } else {
                        logger.warning(testFile.getAbsolutePath() + " could not be run, Ignoring the error.");
                    }
                } finally {
                    this.logger.testEnd(testFile.getAbsolutePath());
                }
            }
        } finally {
            this.logger.finalizeResults();
        }
    }

    private String searchAndReplaceVariables(InterpreterContext context, String str) throws InterpreterException {
        if (str != null) {
            int index = str.indexOf("${");
            while (index != -1) {
                int closeBraceIndex = str.indexOf("}", index);
                String varname = str.substring(index + 2, closeBraceIndex);
                String value = varname;

                int dotIndex = varname.indexOf(".");
                if (dotIndex != -1) {
                    String dsname = varname.substring(0, dotIndex);
                    String xpath = varname.substring(dotIndex + 1);
                    value = context.getDataFromDataset(dsname, xpath);
                } else {
                    value = String.valueOf(context.getVariableValue(varname));
                }

                str = str.substring(0, index) + value + str.substring(closeBraceIndex + 1);
                // reindex
                index = str.indexOf("${");
            }
        }
        return str;
    }

    protected Action fixAction(InterpreterContext context, Action action) throws InterpreterException {
        Action newAction = null;
        try {
            newAction = (Action) ObjectCloner.deepCopy(action);
        } catch (Exception e) {
            throw new InterpreterException("error while cloning action", context);
        }
        newAction.setXpath(searchAndReplaceVariables(context, newAction.getXpath()), false);
        newAction.setValue(searchAndReplaceVariables(context, newAction.getValue()), false);
        return newAction;
    }

    public void runTest(File testFile) throws InterpreterException {
        // Initialize the context
        InterpreterContext context = new InterpreterContext(this.config);
        try {
            context.setDebugDir(this.config.debugDir);
            context.setLogger(this.logger);
            context.clear();
            context.currentFile = testFile.getAbsolutePath();

            if (this.config.easyHttps) {
                logger.info("Enabling EasySSL");
                Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
                Protocol.registerProtocol("https", easyhttps);
            }

            Action[] actions = null;
            try {
                actions = context.loadFile(testFile.getAbsolutePath());
            } catch (Exception e) {
                throw new InterpreterException("Error parsing file: " + testFile.getAbsolutePath(), e, context);
            }

            int newac = -1;
            for (int ac = 0; ac < actions.length; ac++) {
                Action action = fixAction(context, actions[ac]);
                logger.stepStart(action, ac);
                logger.info("isContainer:" + action.isContainer());
                try {
                    // set action in context
                    context.currentAction = action;
                    context.currentActionCount = ac;

                    if (action.type.equals("goto")) {
                        handleAction_goto_url(context, action);
                    } else if ("click".equals(action.type)) {
                        if (this.config.thinkTime != -1) {
                            logger.info("Thinking for " + this.config.thinkTime + " ms");
                            try {
                                Thread.sleep(this.config.thinkTime);
                            } catch (Exception e) {
                            }
                        }
                        handleAction_click(context, action);
                    } else if ("verify-title".equals(action.type)) {
                        handleAction_verify_title(context, action);
                    } else if ("assert-text-exists".equals(action.type)) {
                        handleAction_assert_text_exists(context, action);
                    } else if ("assert-text-does-not-exist".equals(action.type)) {
                        handleAction_assert_text_not_exists(context, action);
                    } else if ("fill".equals(action.type)) {
                        handleAction_fill(context, action);
                    } else if ("select".equals(action.type)) {
                        handleAction_select(context, action);
                    } else if ("check".equals(action.type)) {
                        handleAction_check(context, action);
                    } else if ("alert".equals(action.type)) {
                        logger.warning("TODO: Expecting alert around here: '" + action.value + "'");
                    } else if ("confirm".equals(action.type)) {
                        logger.info("TODO: Expecting confirm around here: '" + action.xpath
                                + "'. Should click ok? : " + action.value);
                    } else if ("wait-for-ms".equals(action.type)) {
                        int wait4ms = Integer.parseInt(action.value);
                        logger.debug("wait start: " + new java.util.Date() + ", will wait for: " + wait4ms + " ms");
                        try {
                            Thread.sleep(wait4ms);
                        } catch (InterruptedException e) {
                        }
                        logger.debug("wait end: " + new java.util.Date());
                    } else if (action.isGotoAction()) {
                        newac = handleAction_goto(context, action);
                    } else if (action.isVariableAction()) {
                        handleAction_setvar(context, action);
                    } else if (action.isContainer()) {
                        boolean evaluationResult = evaluateContainer(context, action);
                        System.out.println("evaluationResult:" + evaluationResult);
                        if (!evaluationResult) {
                            // if not true, jump end + 1
                            newac = context.findEnd4Container(action).actionIndex + 1;
                        } else {
                            // push container, evaluate next
                            context.pushContainer(action);
                        }
                    } else if (action.isEndContainer()) {
                        Action container = context.popContainer();
                        if (container.isCondition() != null) {
                            // nothing to do, just a "if", move on
                        } else {
                            Action.DatasetLoop dsl = container.isDatasetLoop();
                            // loop - increment if it is a dataset loop
                            if (dsl != null) {
                                RuntimeDataset ds = context.getDataset(dsl.dsname);
                                ds.incrementLoop();
                            }
                            // and jump to container, which will evaluate
                            newac = container.actionIndex;
                        }
                    } else {
                        String errMsg = "action not supported: " + action.type;
                        throw new InterpreterException(errMsg, context);
                    }
                } finally {
                    try {
                        logger.info("Final browser url: " + getCurrentUrl(context));
                    } catch (Exception e) {
                        // i don't want to handle any null pointers or such, just ignore it!
                        logger.info("Final browser url: unknown. reason: " + e.getMessage());
                    }
                    logger.stepEnd(ac);

                    if (newac != -1) {
                        logger.info("Jumping to action: " + newac);
                        ac = newac - 1;
                        newac = -1;
                    }
                }
            }

            logger.info("all actions done!");
        } finally {
            context.destroy();
        }
    }

    private String getCurrentUrl(InterpreterContext context) {
        WebWindow currentWindow = context.getWindowListener().getMainWindow();
        return currentWindow.getEnclosedPage().getWebResponse().getUrl().toString();
    }

    protected void handleAction_goto_url(InterpreterContext context, Action action) throws InterpreterException {
        try {
            this.logger.info("changing url to page: " + action.value);
            context.getBrowser().getPage(new java.net.URL(action.value));
        } catch (java.io.IOException e) {
            throw new InterpreterException("error while fetching url: " + action.value, e, context);
        }
    }

    protected boolean handleAction_verify_title(InterpreterContext context, Action action)
            throws InterpreterException {
        if (action.value != null) {
            WebWindow currentWindow = context.getWindowListener().getMainWindow();
            if (currentWindow != null) {
                HtmlPage currentPage = ((HtmlPage) currentWindow.getEnclosedPage());
                if (currentPage != null) {
                    String title = currentPage.getTitleText();
                    if (title == null || !title.equals(action.value)) {
                        // FAILURE: title does not match
                        this.logger.error(
                                "title does not match: expected: '" + action.value + "', got: '" + title + "'");
                        return false;
                    } else {
                        this.logger.info("title match: expected: '" + action.value + "'");
                        return true;
                    }
                } else {
                    throw new InterpreterException("page is null", context);
                }
            } else {
                throw new InterpreterException("current window is null", context);
            }
        } else {
            logger.warning("value for verify-title is null.");
            return false;
        }
    }

    protected void handleAction_setvar(InterpreterContext context, Action action) throws InterpreterException {
        Action.VariableDetails var = action.getVariableDetails();
        String jsString = context.getJsEvalPrefix() + action.xpath;

        HtmlPage page = findFrame(context, action.frameSequence, action.window);
        Object scriptValue = context.getBrowser().getJavaScriptEngine().execute(page, jsString, "custom_script", 1);

        Object varValue = null;
        if (scriptValue != null) {
            if (var.type.equals("bool")) {
                varValue = new Boolean(String.valueOf(scriptValue));
            } else if (var.type.equals("num")) {
                varValue = new Double(String.valueOf(scriptValue));
            } else {
                varValue = scriptValue.toString();
            }
        }
        context.addVariable(var, varValue);
    }

    protected int handleAction_goto(InterpreterContext context, Action action) throws InterpreterException {
        boolean evaluationResult = evaluateGotoAction(context, action);
        if (evaluationResult) {
            logger.debug("Evaluated to true");
            String jump2label = action.getGotoDetails().label;
            int newIndex = findIndexOfActionWithLabel(context.getActions(), jump2label);
            if (newIndex != -1) {
                logger.info("jumping to index " + newIndex + " action=" + context.getActions()[newIndex].getType());
                return newIndex;
            } else {
                throw new RuntimeException("cannot jump to label '" + jump2label + "'. No action with that label.");
            }
        } else {
            logger.debug("goto action evaluated to false. move on.");
            return -1;
        }
    }

    protected boolean handleAction_assert_text_not_exists(InterpreterContext context, Action action)
            throws InterpreterException {
        boolean foundText = findText(context, action.frameSequence, action.window, action.xpath, action.value);
        if (foundText) {
            this.logger.error("assert-text-does-not-exist: Found text: '" + action.value + "'");
        } else {
            this.logger.info("assert-text-does-not-exist: Did not find text: '" + action.value + "'");
        }
        return (!foundText);
    }

    protected void handleAction_click(InterpreterContext context, Action action) throws InterpreterException {
        HtmlElement elementFound = findElement(context, action.frameSequence, action.window, action.xpath);
        if (elementFound == null) {
            throw new InterpreterException("element not found " + action.xpath, context);
        }
        if (elementFound instanceof ClickableElement) {
            try {
                logger.info("Clicking on element: " + action.xpath);
                if (elementFound instanceof HtmlAnchor) {
                    logger.info("  clicking on <a> href:[" + ((HtmlAnchor) elementFound).getHrefAttribute());
                } else if (elementFound instanceof HtmlButton) {
                    logger.info("  clicking on button name:[" + ((HtmlButton) elementFound).getNameAttribute());
                }
                ((ClickableElement) elementFound).click();
            } catch (IOException e) {
                // clicks throw IOException!
                throw new InterpreterException("error while click", e, context);
            }
        } else {
            String errMsg = "cannot click on element: " + elementFound.getClass().getName();
            throw new InterpreterException(errMsg, context);
        }
    }

    protected boolean handleAction_assert_text_exists(InterpreterContext context, Action action)
            throws InterpreterException {
        boolean foundText = findText(context, action.frameSequence, action.window, action.xpath, action.value);
        if (foundText) {
            this.logger.info("assert-text-exists: Found text: '" + action.value + "'");
        } else {
            this.logger.error("assert-text-exists: Could not find text: '" + action.value + "'.");
        }
        return foundText;
    }

    protected void handleAction_check(InterpreterContext context, Action action) throws InterpreterException {
        HtmlElement elementFound = findElement(context, action.frameSequence, action.window, action.xpath);
        if (elementFound == null) {
            throw new InterpreterException("element not found " + action.xpath, context);
        }
        if (elementFound instanceof HtmlCheckBoxInput) {
            ((HtmlCheckBoxInput) elementFound).setChecked((new Boolean(action.value)).booleanValue());
        } else {
            throw new InterpreterException("cannot check/uncheck: " + elementFound.getClass().getName(), context);
        }
    }

    protected void handleAction_fill(InterpreterContext context, Action action) throws InterpreterException {
        HtmlElement elementFound = findElement(context, action.frameSequence, action.window, action.xpath);
        if (elementFound == null) {
            throw new InterpreterException("element not found " + action.xpath, context);
        }
        if (elementFound instanceof HtmlInput) {
            ((HtmlInput) elementFound).setValueAttribute(action.value);
        } else if (elementFound instanceof HtmlTextArea) {
            ((HtmlTextArea) elementFound).setText(action.value);
        } else {
            throw new InterpreterException("cannot fill: " + elementFound.getClass().getName(), context);
        }
    }

    protected void handleAction_select(InterpreterContext context, Action action) throws InterpreterException {
        HtmlElement elementFound = findElement(context, action.frameSequence, action.window, action.xpath);
        if (elementFound == null) {
            throw new InterpreterException("element not found " + action.xpath, context);
        }
        if (elementFound instanceof HtmlSelect) {
            HtmlSelect select = (HtmlSelect) elementFound;
            if (!select.isMultipleSelectEnabled()) {
                HtmlOption option = null;
                try {
                    option = select.getOptionByValue(action.value);
                } catch (com.gargoylesoftware.htmlunit.ElementNotFoundException e) {
                    logger.warning("cannot find value: [" + action.value + "] in the select: [" + action.xpath
                            + "]. Will try search text values");
                }
                if (option != null) {
                    select.setSelectedAttribute(option, true);
                } else {
                    List options = select.getOptions();
                    boolean found = false;
                    if (options != null) {
                        for (int i = 0; i < options.size(); i++) {
                            option = (HtmlOption) options.get(i);
                            logger.debug("comparing value in select: [" + option.asText() + "] to expected value: ["
                                    + action.value + "]");
                            if (option.asText() != null && option.asText().equals(action.value)) {
                                select.setSelectedAttribute(option, true);
                                found = true;
                            }
                        }
                    } else {
                        logger.warning("no options in the select: " + action.xpath);
                    }
                    if (!found) {
                        throw new InterpreterException(
                                "cannot find '" + action.value + "' as one of the values for select", context);
                    }
                }
            } else {
                // reset all options
                List options = select.getOptions();
                for (int i = 0; i < options.size(); i++) {
                    logger.debug("deselecting value: " + ((HtmlOption) options.get(i)).getValueAttribute());
                    ((HtmlOption) options.get(i)).setSelected(false);
                }
                String[] values = action.value.split(",");
                for (int i = 0; i < values.length; i++) {
                    try {
                        values[i] = java.net.URLDecoder.decode(values[i], "utf-8");
                    } catch (UnsupportedEncodingException e) {
                        throw new InterpreterException(e, context);
                    }
                    HtmlOption option = select.getOptionByValue(values[i]);
                    if (option != null) {
                        logger.debug("selecting value: " + option.getValueAttribute());
                        option.setSelected(true);
                    } else {
                        throw new InterpreterException(
                                "cannot find '" + values[i] + "' as one of the values for select", context);
                    }
                }
            }
        } else {
            throw new InterpreterException("cannot select values for : " + elementFound.getClass().getName(),
                    context);
        }
    }

    private int findIndexOfActionWithLabel(Action[] actions, String label) {
        for (int i = 0; actions != null && i < actions.length; i++) {
            if (actions[i].getLabel().equals(label)) {
                return i;
            }
        }
        return -1;
    }

    protected boolean evaluateContainer(InterpreterContext context, Action action) {
        Object scriptValue = null;
        HtmlPage currentPage = findFrame(context, action.frameSequence, action.window);
        JavaScriptEngine scriptEngine = context.getBrowser().getJavaScriptEngine();
        String jsPrefix = context.getJsEvalPrefix();

        Action.Condition condition = action.isConditionLoop();
        if (condition == null) {
            condition = action.isCondition();
        }

        Action.DatasetLoop dsl = action.isDatasetLoop();

        if (condition != null) {
            Object lhsValue = scriptEngine.execute(currentPage, jsPrefix + ";" + action.xpath, "custom_script", 1);
            Object rhsValue = scriptEngine.execute(currentPage, jsPrefix + ";" + action.value, "custom_script", 1);
            System.out.println("lhs=" + lhsValue);
            System.out.println("rhs=" + rhsValue);
            if (lhsValue == null && rhsValue == null) {
                return true;
            } else if (lhsValue == null || rhsValue == null) {
                return false;
            } else {
                boolean bothNumbers = true;
                try {
                    Float.parseFloat(String.valueOf(lhsValue));
                    Float.parseFloat(String.valueOf(rhsValue));
                } catch (NumberFormatException e) {
                    bothNumbers = false;
                }

                String evalStr = null;

                if (bothNumbers) {
                    evalStr = String.valueOf(lhsValue) + " " + convertOperatorString(condition.operator) + " "
                            + String.valueOf(rhsValue);
                } else {
                    evalStr = "'" + String.valueOf(lhsValue) + "' " + convertOperatorString(condition.operator)
                            + " '" + String.valueOf(rhsValue) + "'";
                }

                System.out.println("evalScript [" + evalStr + "]");
                scriptValue = scriptEngine.execute(currentPage, evalStr, "custom_script", 1);
                System.out.println("scriptValue:" + scriptValue);

                boolean retValue = new Boolean(scriptValue + "").booleanValue();
                return retValue;
            }
        } else if (dsl != null) {
            RuntimeDataset ds = context.getDataset(dsl.dsname);
            return ds.hasMore();
        } else {
            throw new RuntimeException("cannot evaluate this container!");
        }
    }

    private String convertOperatorString(String op) {
        if (op.equals("eq")) {
            return "==";
        } else if (op.equals("gt")) {
            return ">";
        } else if (op.equals("lt")) {
            return "<";
        } else if (op.equals("ge")) {
            return ">=";
        } else if (op.equals("le")) {
            return "<=";
        } else if (op.equals("ne")) {
            return "!=";
        } else {
            throw new RuntimeException("operator not recognized!" + op);
        }
    }

    /*
    private boolean evaluateGotoAction_2(InterpreterContext context, Action action) {
    Object scriptValue = context.getBrowser().getScriptEngine().execute(
            findFrame(context, action.frameSequence, action.window), action.xpath, "custom_script");
        
    String output = scriptValue + "";
        
    Action.GotoDetails gd = action.getGotoDetails();
        
    if (gd.operateOn.equals("bool") || gd.operateOn.equals("str")) {
        if (gd.operator.equals("eq")) {
            return output.equals(action.value);
        } else if (gd.operator.equals("ne")) {
            return ! output.equals(action.value);
        } else {
            throw new RuntimeException("operator '" + gd.operator + " is not supported for " + gd.operateOn);
        }
    } else {
    }
    }
        
    private boolean evaluateGotoAction_1(InterpreterContext context, Action action) {
    Object scriptValue = context.getBrowser().getScriptEngine().execute(
            findFrame(context, action.frameSequence, action.window), action.xpath, "custom_script");
        
    String output = scriptValue + "";
        
    Action.GotoDetails gd = action.getGotoDetails();
        
    if (gd.operateOn.equals("bool") || gd.operateOn.equals("str")) {
        if (gd.operator.equals("eq")) {
            return output.equals(action.value);
        } else if (gd.operator.equals("ne")) {
            return ! output.equals(action.value);
        } else {
            throw new RuntimeException("operator '" + gd.operator + " is not supported for " + gd.operateOn);
        }
    } else {
    }
    }
        
    private boolean evaluateGotoAction_3(InterpreterContext context, Action action) {
    Object scriptValue = context.getBrowser().getScriptEngine().execute(
            findFrame(context, action.frameSequence, action.window), action.xpath, "custom_script");
        
    String output = scriptValue + "";
        
    Action.GotoDetails gd = action.getGotoDetails();
        
    if (gd.operateOn.equals("bool") || gd.operateOn.equals("str")) {
        if (gd.operator.equals("eq")) {
            return output.equals(action.value);
        } else if (gd.operator.equals("ne")) {
            return ! output.equals(action.value);
        } else {
            throw new RuntimeException("operator '" + gd.operator + " is not supported for " + gd.operateOn);
        }
    } else {
    }
    }
    */

    private boolean evaluateGotoAction(InterpreterContext context, Action action) {
        Object scriptValue = context.getBrowser().getJavaScriptEngine()
                .execute(findFrame(context, action.frameSequence, action.window), action.xpath, "custom_script", 1);

        String output = scriptValue + "";

        Action.GotoDetails gd = action.getGotoDetails();

        if (gd.operateOn.equals("bool") || gd.operateOn.equals("str")) {
            if (gd.operator.equals("eq")) {
                return output.equals(action.value);
            } else if (gd.operator.equals("ne")) {
                return !output.equals(action.value);
            } else {
                throw new RuntimeException("operator '" + gd.operator + " is not supported for " + gd.operateOn);
            }
        } else if (gd.operateOn.equals("num")) {
            float outputF = Float.parseFloat(output);
            float compareTo = Float.parseFloat(action.value);

            if (gd.operator.equals("eq")) {
                return outputF == compareTo;
            } else if (gd.operator.equals("ne")) {
                return outputF != compareTo;
            } else if (gd.operator.equals("gt")) {
                return outputF > compareTo;
            } else if (gd.operator.equals("ge")) {
                return outputF >= compareTo;
            } else if (gd.operator.equals("lt")) {
                return outputF < compareTo;
            } else if (gd.operator.equals("le")) {
                return outputF <= compareTo;
            } else {
                throw new RuntimeException("operator '" + gd.operator + " is not supported for num.");
            }
        } else if (gd.operateOn.equals("loop_on_dataset")) {
            RuntimeDataset ds = context.getDataset(gd.operator);
            ds.incrementLoop();
            return ds.hasMore();
        } else {
            throw new RuntimeException("operation on '" + gd.operateOn + " is not supported.");
        }
    }

    private boolean findText(InterpreterContext context, String frameSequence, String winref, String xpathStr,
            String txt) throws InterpreterException {
        HtmlPage currentPage = findFrame(context, frameSequence, winref);
        if (currentPage != null) {
            String pageTxt = currentPage.asText();
            if (pageTxt.indexOf(txt) != -1) {
                return true;
            } else {
                return false;
            }
        } else {
            throw new InterpreterException("current page is null", context);
        }
    }

    private HtmlPage findFrame(InterpreterContext context, String frameSequence, String winref) {
        HtmlPage currentPage = null;
        WebWindow currentWindow;

        currentWindow = context.getWindowListener().getWindowByName(winref);

        if (currentWindow != null) {
            currentPage = ((HtmlPage) currentWindow.getEnclosedPage());
            if (!"".equals(frameSequence)) {
                logger.debug("looking for frame-sequence: " + frameSequence);
                String[] frames = frameSequence.split(",");
                for (int i = frames.length - 1; i >= 0; i--) {
                    String frame = frames[i];
                    this.logger.debug("Looking for frame: " + frame + ", current page name: "
                            + currentPage.getEnclosingWindow().getName());
                    FrameWindow fws[] = (FrameWindow[]) currentPage.getFrames().toArray(new FrameWindow[0]);
                    for (int j = 0; j < fws.length; j++) {
                        logger.debug("have frame: " + fws[j].getName());
                    }
                    FrameWindow fw = currentPage.getFrameByName(frame);
                    if (fw != null) {
                        currentPage = (HtmlPage) fw.getEnclosedPage();
                        this.logger.debug("found frame: " + frame);
                    } else {
                        logger.fatal(
                                (i + 1) + " frame '" + frame + "' not found. Framesequence was " + frameSequence);
                        return null;
                    }
                }
            }
        } else {
            logger.fatal("cannot find window by name: '" + winref + "'");
        }
        return currentPage;
    }

    private HtmlElement findElement(InterpreterContext context, String frameSequence, String winref,
            String xpathStr) {
        HtmlPage currentPage = findFrame(context, frameSequence, winref);
        if (currentPage != null) {
            HtmlElement elementFound = findElement(context, currentPage, xpathStr);
            if (elementFound == null) {
                // wait for a couple of seconds?
                try {
                    Thread.sleep(2000);
                } catch (Exception e) {
                }
                elementFound = findElement(context, currentPage, xpathStr);
                if (elementFound != null) {
                    return elementFound;
                } else {
                    logger.fatal("element not found " + xpathStr);
                    return null;
                }
            } else {
                return elementFound;
            }
        } else {
            logger.fatal("current page is null");
        }
        return null;
    }

    private HtmlElement findElement(InterpreterContext context, HtmlPage page, String xpathStr) {
        TG4W_XPath xpath = null;
        try {
            xpath = TG4W_XPathParser.parseXPath(xpathStr);
            context.xpath = xpath;
        } catch (TG4W_XPathParseError e) {
            logger.fatal("could not parse xpath: " + xpathStr);
        }
        TG4W_XPathPart parts[] = xpath.getParts();
        boolean findRecursive = false;
        HtmlElement elementFound = page.getDocumentElement();
        for (int xpathPartCount = 0; xpathPartCount < parts.length; xpathPartCount++) {
            TG4W_XPathPart part = parts[xpathPartCount];
            context.xpathPart = part;
            String elementName = part.getElementName();
            if (elementName.equals("*")) {
                findRecursive = true;
            } else {
                TG4W_XPathPredicate predicates[] = part.getPredicates();
                if (predicates.length == 1 && predicates[0].isId()) {
                    try {
                        elementFound = elementFound.getHtmlElementById(predicates[0].getValue());
                    } catch (ElementNotFoundException e) {
                        logger.warning("ElementNotFoundException thrown: " + e.getMessage());
                        elementFound = null;
                    }
                    if (elementFound == null) {
                        logger.fatal("Element with ID not found: " + predicates[0].getValue());
                        return null;
                    }
                } else {
                    if (true) {
                        logger.info("finding generic element name '" + elementName + "'");
                        List list = elementFound.getHtmlElementsByTagName(elementName.toLowerCase());
                        if (list == null || list.size() == 0) {
                            logger.fatal("element " + elementName + " not found!");
                            return null;
                        } else {
                            if (predicates.length == 1 && predicates[0].isNumber()) {
                                List topLevelList = this.filterOnlyTopLevel(list, elementFound,
                                        elementName.toLowerCase());
                                // negate 1 because xpath predicates always start from 1
                                int indexReq = (int) Float.parseFloat(predicates[0].getValue()) - 1;
                                if (indexReq < topLevelList.size()) {
                                    elementFound = (HtmlElement) topLevelList.get(indexReq);
                                } else {
                                    logger.fatal("Requesting index: " + (indexReq + 1) + " list has only "
                                            + topLevelList.size() + " elements");
                                    return null;
                                }
                            } else {
                                for (int el = 0; el < list.size(); el++) {
                                    HtmlElement e2check = (HtmlElement) list.get(el);
                                    boolean success = true;
                                    for (int pc = 0; success && pc < predicates.length; pc++) {
                                        String name = predicates[pc].getAttrName();
                                        String val = predicates[pc].getValue();
                                        String eAttrVal = "";
                                        if (name.equals("CDATA")) {
                                            eAttrVal = HtmlUnitWorkArounds.elementAsText(e2check);
                                        } else {
                                            if (e2check.isAttributeDefined(name)) {
                                                eAttrVal = e2check.getAttributeValue(name);
                                            } else {
                                                // if there is no such attribute, this is ruled out
                                                success = false;
                                            }
                                        }
                                        logger.debug("Comparing: '" + eAttrVal + "' and expected: '" + val + "'");
                                        if (!eAttrVal.equals(val)) {
                                            success = false;
                                        }
                                    }
                                    if (success) {
                                        elementFound = e2check;
                                        break;
                                    } else if (el == list.size() - 1) {
                                        logger.fatal(
                                                "element " + elementName + " not found!. Ran out of elements!");
                                        return null;
                                    } else {
                                        // move on to the next
                                    }
                                }
                            }
                        }
                    }
                }
                findRecursive = false;
            }
        }
        return elementFound;
    }

    private List filterOnlyTopLevel(List list, HtmlElement parentNode, String tagName) {
        List newList = new LinkedList();
        String parentXpath = getXpath(parentNode);
        for (int i = 0; i < list.size(); i++) {
            HtmlElement e = (HtmlElement) list.get(i);
            String xpath = getXpath(e);
            xpath = xpath.substring(parentXpath.length());
            if (xpath.split("/" + tagName.toLowerCase()).length == 2) {
                newList.add(e);
                logger.debug("Adding: " + xpath);
            } else {
                logger.debug("Discarding: " + xpath);
            }
        }
        return newList;
    }

    private String getXpath(DomNode e) {
        String path = e.getNodeName();
        DomNode parent = e.getParentNode();
        if (parent != null) {
            Iterator children = parent.getChildIterator();
            int counter = 0;
            while (children.hasNext()) {
                DomNode child = (DomNode) children.next();
                if (child.equals(e)) {
                    break;
                } else {
                    counter++;
                }
            }
            path = getXpath(parent) + "/" + path + "[" + counter + "]";
        }
        return path;
    }

    public static void main(String args[]) throws InterpreterException, NumberFormatException, IOException {
        InterpreterConfig config = generateConfigFromCmdLineArgs(args, false);
        config.dump();
        HtmlUnitInterpreter interpreter = new HtmlUnitInterpreter(config);

        if (config.files2run == null && config.dirs2run == null) {
            System.out.println("--input-files or --input-dirs is a required paramter");
            System.exit(1);
        }

        if (config.files2run != null) {
            for (int i = 0; i < config.files2run.length; i++) {
                interpreter.addTestFile(config.files2run[i].trim());
            }
        }
        try {
            interpreter.runAll();
        } catch (InterpreterException e) {
            System.out.println("------------- ERROR ---------------- ");
            System.out.println(e.getMessage());
            System.out.println("------------------------------------ ");
        }

        //System.exit(0);
    }

    public static InterpreterConfig generateConfigFromCmdLineArgs(String[] args, boolean defaultLogger)
            throws IOException {
        CommandLineParser parser = new CommandLineParser(args);
        String files2run = parser.getValue("input-files");
        String dirs2run = parser.getValue("input-dirs");
        String recurse = parser.getValue("recurse");
        String pattern = parser.getValue("pattern");
        String debugDir = parser.getValue("debug-dir");
        String debugLevel = parser.getValue("debug-level", "DEBUG");
        String datasetMap = parser.getValue("dataset-map", null);
        boolean noEasyHttps = parser.hasKey("no-easy-https");
        boolean enableUI = parser.hasKey("enable-ui");
        boolean enableCaching = parser.hasKey("enable-caching");
        long thinkTime = parser.getLongValue("think-time", -1);
        debugLevel = debugLevel.toUpperCase();

        if (debugLevel.equals(TestResult.LOG_NAME[TestResult.LOG_CRAZY])) {
            System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
            System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
            System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire.header", "debug");
            System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "debug");
            System.getProperties().put("org.apache.commons.logging.simplelog.defaultlog", "trace");
        }

        InterpreterConfig config = new InterpreterConfig();
        config.debugDir = debugDir;
        config.debugLevel = debugLevel;
        config.swingUiEnabled = enableUI;
        config.easyHttps = !noEasyHttps;
        config.enableCaching = enableCaching;
        config.thinkTime = thinkTime;
        config.datasetMap = datasetMap;

        if (files2run != null) {
            config.files2run = files2run.split(",");
        }
        if (dirs2run != null) {
            config.dirs2run = dirs2run.split(",");
        }

        if (defaultLogger) {
            String filename = config.reportFile;
            if (!(new File(filename)).isAbsolute() && config.debugDir != null) {
                filename = config.debugDir + File.separator + filename;
            }
            config.logger = new XmlTestResultImpl(filename, null);
            config.logger.setDebugLevel(config.debugLevel);
        }
        return config;
    }

}