com.redskyit.scriptDriver.RunTests.java Source code

Java tutorial

Introduction

Here is the source code for com.redskyit.scriptDriver.RunTests.java

Source

// Copyright 2014, RedSky IT
// This software is release under the MIT License.
// See LICENSE file for details.
//
package com.redskyit.scriptDriver;

// API documentation: https://seleniumhq.github.io/selenium/docs/api/java/

import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.zip.CRC32;
import java.time.Duration;

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.By;

import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Sleeper;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.logging.LogEntries;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.logging.Logs;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class RunTests {

    // Execution selection represents an executable chunk of source code, be that a script from file
    // or the body of a function/alias.  An execution selection has a body and optionally some arguments.
    private class ExecutionContext {
        private List<String> params = null;
        private String body = null;
        private List<Object> args = null;

        public ExecutionContext() {
        }

        public ExecutionContext(List<String> params, String body) {
            this.params = params;
            this.body = body;
            this.args = new ArrayList<Object>();
        }

        public String getBody() {
            return body;
        }

        public List<String> getParams() {
            return params;
        }

        public void addArg(Object arg) {
            if (null == args)
                throw new Error("Script: cannot add argument to alias without parameters");
            args.add(arg);
        }

        public Object getArg(String name) {
            if (args != null && args.size() > 0) {
                for (int i = 0; i < params.size(); i++) {
                    if (params.get(i).equals(name)) {
                        return args.get(i);
                    }
                }
            }
            return null;
        }

        public String getExpandedString(String str) {
            // Token is a word (rather than quoted string).  We only support $name format
            // variable substitution in words
            if (args != null && args.size() > 0) {
                for (int i = 0; i < params.size(); i++) {
                    Object v = args.get(i);
                    if (v.getClass() == Double.class) {
                        Integer value = new Integer(((Double) v).intValue());
                        str = str.replace("$I(" + params.get(i) + ")", value.toString());
                    }
                    str = str.replace("$(" + params.get(i) + ")", args.get(i).toString());
                }
            }
            return str;
        }

        public String getExpandedString(StreamTokenizer tokenizer) {
            // Token is a word (rather than quoted string).  We only support $name format
            // variable substitution in words
            if (args != null && args.size() > 0) {
                if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
                    if (tokenizer.sval.startsWith("$")) {
                        Object repl = this.getArg(tokenizer.sval.substring(1));
                        if (null != repl)
                            return repl.toString();
                    }
                    if (tokenizer.sval.startsWith("\\$")) {
                        return tokenizer.sval.substring(1);
                    }
                }
                if (tokenizer.ttype == '"') {
                    // token is a quoted string
                    return this.getExpandedString(tokenizer.sval);
                }
            }
            return tokenizer.sval;
        }

        public double getExpandedNumber(StreamTokenizer tokenizer) {
            // Token is a word (rather than quoted string).  We only support $name format
            // variable substitution in words
            if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                return tokenizer.nval;
            }
            if (args != null && args.size() > 0) {
                if (tokenizer.ttype == StreamTokenizer.TT_WORD && tokenizer.sval.startsWith("$")) {
                    Object repl = this.getArg(tokenizer.sval.substring(1));
                    if (null != repl) {
                        if (repl.getClass() == Double.class) {
                            return ((Double) repl).doubleValue();
                        }
                    }
                }
                throw new Error("Argument is not a number");
            }
            throw new Error("Arguments are missing from function call");
        }

        public void clearArgs() {
            if (args != null)
                args.clear();
        }
    };

    enum SelectionType {
        None, Field, Select, Script, XPath
    };

    ChromeOptions options = null;
    Map<String, Object> prefs = null;
    ChromeDriver driver = null;
    Actions actions = null;
    RemoteWebElement selection = null;
    SelectionType stype = SelectionType.None;
    String selector = null;
    String selectionCommand = null;
    private String screenShotPath = null;
    private long _waitFor = 0;
    private long _defaultWaitFor = 5000;
    private boolean _if;
    private boolean _test;
    private boolean _skip;
    private boolean _not;
    HashMap<String, ExecutionContext> functions = new HashMap<String, ExecutionContext>();
    private boolean autolog = false;
    private Dimension chrome = new Dimension(0, 0);
    private HashMap<String, ArrayList<Object>> stacks = new HashMap<String, ArrayList<Object>>();

    private static String version = "0.5.2";

    @SuppressWarnings("serial")
    public class RetryException extends Exception {
        public RetryException(String message) {
            super(message);
        }
    };

    abstract class WaitFor {
        public WaitFor(String cmd, StreamTokenizer tokenizer, boolean requiresContext) throws Exception {
            if (requiresContext && null == selection) {
                throw new Exception(cmd + " command requires a field selection at line " + tokenizer.lineno());
            }
            int retry = 0;
            long now = 0;
            do {
                try {
                    this.run();
                    return;
                } catch (StaleElementReferenceException e) {
                    // element has gone stale, re-select it
                    System.out.println("// EXCEPTION : StaleElementReference : " + e.getMessage().split("\n")[0]);
                    retry++;
                } catch (InvalidElementStateException is) {
                    System.out.println(
                            "// EXCEPTION : InvalidElementStateException : " + is.getMessage().split("\n")[0]);
                    scrollContextIntoView(selection);
                    retry++;
                } catch (WebDriverException e2) {
                    System.out.println("// EXCEPTION : WebDriverException : " + e2.getMessage().split("\n")[0]);
                    // Try and auto-recover by scrolling this element into view
                    scrollContextIntoView(selection);
                    retry++;
                } catch (RetryException r) {
                    System.out.println("// EXCEPTION : RetryException : " + r.getMessage().split("\n")[0]);
                    // Try and auto-recover by scrolling this element into view
                    retry++;
                } catch (Exception e3) {
                    System.out.println("// EXCEPTION : " + e3.getMessage().split("\n")[0]);
                    if (retry++ > 3)
                        this.fail(e3);
                }
                // attempt to recover
                now = (new Date()).getTime();
                System.out.println(
                        now + ": DEBUG: retry=" + retry + " calling sleepAndReselect(100) _waitFor = " + _waitFor);
                if (retry == 1 && now >= _waitFor) {
                    System.out.println("// Wait timer already expired, apply default wait timer");
                    System.out.println("// wait " + (_defaultWaitFor * 1.0) / 1000.0);
                    _waitFor = (long) now + _defaultWaitFor;
                }
                sleepAndReselect(100);
            } while (_waitFor > 0 && now < _waitFor);

            // action failed
            info(selection, selectionCommand, false);
            _waitFor = 0; // wait timer expired
            this.fail(new Exception(cmd + " failed at line " + tokenizer.lineno()));
        }

        protected abstract void run() throws Exception;

        protected void fail(Exception e) throws Exception {
            throw e;
        }
    }

    public RunTests() throws IOException {
    }

    public static void main(String[] args) throws IOException {
        RunTests app = new RunTests();
        int exitstatus = app.run(args);
        System.exit(exitstatus);
    }

    public int run(String[] args) {
        File source = null;
        String onexit = "--onsuccess";
        int exitstatus = 0;
        int i = 0;
        try {
            while (i < args.length) {
                source = runScript(args[i]);
                ++i;
            }
        } catch (Exception e) {
            System.out.println(args[i]);
            e.printStackTrace();
            onexit = "--onfail";
            exitstatus = 1;
        }

        // If we have an onexit handler to call, then run it now
        if (null != onexit) {
            try {
                executeFunction(onexit, source, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                exitstatus = 2;
            }
        }

        // Cleanup
        if (null != driver) {
            driver.quit();
        }

        return exitstatus;
    }

    private void initTokenizer(StreamTokenizer tokenizer) {
        tokenizer.quoteChar('"');
        tokenizer.slashStarComments(true);
        tokenizer.slashSlashComments(true);
        tokenizer.whitespaceChars(' ', ' ');
        tokenizer.whitespaceChars(0x09, 0x09);
        tokenizer.wordChars('$', '$'); // treat $ as part of word
        tokenizer.wordChars('#', '#'); // treat # as part of word
        tokenizer.wordChars('_', '_'); // treat $# as part of word
    }

    private StreamTokenizer openScript(File file) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF8"));
        StreamTokenizer tokenizer = new StreamTokenizer(in);
        initTokenizer(tokenizer);
        return tokenizer;
    }

    private StreamTokenizer openString(String code) {
        // StreamTokenizer tokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(code.getBytes()))));
        StreamTokenizer tokenizer = new StreamTokenizer(new CharArrayReader(code.toCharArray()));
        initTokenizer(tokenizer);
        return tokenizer;
    }

    private File runScript(String filename) throws Exception {
        File file = new File(filename);
        StreamTokenizer tokenizer = openScript(file);
        while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
            if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
                runCommand(tokenizer, file, file.getName(), new ExecutionContext());
            }
        }
        return file;
    }

    private boolean executeFunction(String name, File file, StreamTokenizer parent, ExecutionContext script)
            throws Exception {
        ExecutionContext context = functions.get(name);
        if (null != context) {
            // If this context has parameters then gather argument values
            if (null != parent && null != script) {
                List<String> params = context.getParams();
                if (null != params && params.size() > 0) {
                    context.clearArgs();
                    int count = params.size();
                    while (count-- > 0) {
                        // get argument
                        parent.nextToken();
                        System.out.print(' ');
                        switch (parent.ttype) {
                        case StreamTokenizer.TT_NUMBER:
                            System.out.print(parent.nval);
                            context.addArg(parent.nval);
                            break;
                        default:
                            System.out.print(parent.sval);
                            context.addArg(script.getExpandedString(parent));
                            break;
                        }
                    }
                }
                System.out.println();
            }

            // Get function body and execute it
            String code = context.getBody();
            if (null != code) {
                StreamTokenizer tokenizer = openString(code);
                while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
                    if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
                        runCommand(tokenizer, file, name, context);
                    }
                }
                return true;
            }
        }
        return false;
    }

    private boolean runString(String source, File file, String cmd) throws Exception {
        StreamTokenizer tokenizer = openString(source);
        while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
            if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
                runCommand(tokenizer, file, cmd, null);
            }
        }
        return true;
    }

    // Argument parsing: { token token { token token } token }

    private interface BlockHandler {
        void parseToken(StreamTokenizer tokenizer, String arg) throws IOException;
    }

    private void parseBlock(ExecutionContext script, StreamTokenizer tokenizer, BlockHandler handler)
            throws Exception {
        tokenizer.nextToken();
        if (tokenizer.ttype == '{') {
            int braceLevel = 1;
            System.out.print(" {");
            tokenizer.eolIsSignificant(true);
            tokenizer.nextToken();
            while (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"'
                    || tokenizer.ttype == StreamTokenizer.TT_NUMBER || tokenizer.ttype == '\n'
                    || tokenizer.ttype == '{' || tokenizer.ttype == '}' || tokenizer.ttype == ','
                    || tokenizer.ttype == '*' || tokenizer.ttype == ':') {
                System.out.print(' ');
                String arg;
                switch (tokenizer.ttype) {
                case '{':
                    braceLevel++;
                    arg = "{";
                    break;
                case '}':
                    if (--braceLevel == 0) {
                        System.out.print("}");
                        tokenizer.eolIsSignificant(false);
                        return;
                    }
                    arg = "}";
                    break;
                case StreamTokenizer.TT_NUMBER:
                    arg = String.valueOf(tokenizer.nval);
                    break;
                case StreamTokenizer.TT_EOL:
                    arg = "\n";
                    break;
                case '"':
                    // 5 backslashed required in replace string because its processed once
                    // as a string (yielding \\") and then again by the replace method
                    // of the regular expression (so \\" becomes \")
                    arg = '"' + script.getExpandedString(tokenizer).replaceAll("\"", "\\\\\"") + '"';
                    break;
                case ',':
                    arg = ",";
                    break;
                case ':':
                    arg = ":";
                    break;
                case '*':
                    arg = "*";
                    break;
                default:
                    arg = script.getExpandedString(tokenizer);
                }
                System.out.print(arg);
                handler.parseToken(tokenizer, arg);
                tokenizer.nextToken();
            }
            System.out.println();
            throw new Exception("args unexpectd token " + tokenizer.ttype);
        }
        tokenizer.pushBack(); // no arguments
    }

    private class Block implements BlockHandler {
        char sep = ' ';
        char isep = sep;
        boolean quoteWords = false;
        String args = null;

        public Block(char sep, boolean quoteWords) {
            this.isep = this.sep = sep;
            this.quoteWords = quoteWords;
        }

        public void parseToken(StreamTokenizer tokenizer, String arg) {
            if (quoteWords && tokenizer.ttype == StreamTokenizer.TT_WORD) {
                arg = '"' + arg + '"';
            }
            if (tokenizer.ttype == ',') {
                sep = ','; // next word joined by comma
                return;
            }
            if (tokenizer.ttype == ':') {
                sep = ':';
                return;
            }
            if (tokenizer.ttype == '*') {
                arg = "*";
            }
            args = args == null ? arg : args + sep + arg;
            sep = isep;
        }

        String get() {
            return args;
        }
    };

    private class ArgArray implements BlockHandler {
        List<String> args = new ArrayList<String>();

        public void parseToken(StreamTokenizer tokenizer, String arg) {
            if (tokenizer.ttype == '"' || tokenizer.ttype == '\'') {
                arg = arg.substring(1, arg.length() - 1);
            }
            args.add(arg);
        }

        List<String> get() {
            return args;
        }
    };

    private String getBlock(ExecutionContext script, StreamTokenizer tokenizer, char sep, boolean quoteWords)
            throws Exception {
        Block block = new Block(sep, quoteWords);
        parseBlock(script, tokenizer, block);
        return block.get();
    }

    private List<String> getArgs(StreamTokenizer tokenizer, ExecutionContext script) throws Exception {
        ArgArray args = new ArgArray();
        parseBlock(script, tokenizer, args);
        return args.get();
    }

    // Param parsing: (name, name, name)

    private interface ParamHandler {
        void processParam(StreamTokenizer tokenizer, String arg) throws IOException;
    }

    private void parseParams(StreamTokenizer tokenizer, ParamHandler handler) throws Exception {
        tokenizer.nextToken();
        if (tokenizer.ttype == '(') {
            System.out.print(" (");
            tokenizer.nextToken();
            while (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == ',' || tokenizer.ttype == ')') {
                System.out.print(' ');
                String arg;
                switch (tokenizer.ttype) {
                case ',':
                    arg = ",";
                    break;
                case ')':
                    System.out.print(")");
                    return;
                default:
                    arg = tokenizer.sval;
                }
                System.out.print(arg);
                handler.processParam(tokenizer, arg);
                tokenizer.nextToken();
            }
            System.out.println();
            throw new Exception("args unexpectd token " + tokenizer.ttype);
        }
        tokenizer.pushBack(); // no arguments
    }

    private class Params implements ParamHandler {
        List<String> args = new ArrayList<String>();

        public Params() {
        }

        public void processParam(StreamTokenizer tokenizer, String arg) throws IOException {
            if (!arg.equals(",")) {
                args.add(arg);
            }
        }

        List<String> get() {
            return args;
        }
    };

    private List<String> getParams(StreamTokenizer tokenizer) throws Exception {
        Params params = new Params();
        parseParams(tokenizer, params);
        return params.get();
    }

    private void runCommand(final StreamTokenizer tokenizer, File file, String source, ExecutionContext script)
            throws Exception {
        // Automatic log dumping
        if (autolog && null != driver) {
            dumpLog();
        }

        String cmd = tokenizer.sval;
        System.out.printf((new Date()).getTime() + ": [%s,%d] ", source, tokenizer.lineno());
        System.out.print(tokenizer.sval);

        if (cmd.equals("version")) {
            // HELP: version
            System.out.println();
            System.out.println("ScriptDriver version " + version);
            return;
        }

        if (cmd.equals("browser")) {
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                System.out.print(' ');
                System.out.print(tokenizer.sval);

                if (tokenizer.sval.equals("prefs")) {
                    // HELP: browser prefs ...
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                        System.out.print(' ');
                        System.out.print(tokenizer.sval);
                        String pref = tokenizer.sval;
                        tokenizer.nextToken();
                        System.out.print(' ');
                        if (_skip)
                            return;
                        if (null == options)
                            options = new ChromeOptions();
                        if (null == prefs)
                            prefs = new HashMap<String, Object>();
                        switch (tokenizer.ttype) {
                        case StreamTokenizer.TT_WORD:
                        case '"':
                            System.out.println(tokenizer.sval);
                            if (tokenizer.sval.equals("false")) {
                                prefs.put(pref, false);
                            } else if (tokenizer.sval.equals("true")) {
                                prefs.put(pref, true);
                            } else {
                                prefs.put(pref, tokenizer.sval);
                            }
                            return;
                        case StreamTokenizer.TT_NUMBER:
                            System.out.println(tokenizer.nval);
                            prefs.put(pref, tokenizer.nval);
                            return;
                        }
                    }
                    System.out.println();
                    throw new Exception("browser option command argument missing");
                }

                if (tokenizer.sval.equals("option")) {
                    // HELP: browser option ...
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') { // expect a quoted string
                        System.out.print(' ');
                        System.out.println(tokenizer.sval);
                        if (_skip)
                            return;
                        if (null == options)
                            options = new ChromeOptions();
                        options.addArguments(tokenizer.sval);
                        return;
                    }
                    System.out.println();
                    throw new Exception("browser option command argument missing");
                }

                // HELP: browser wait <seconds>
                if (tokenizer.sval.equals("wait")) {
                    tokenizer.nextToken();
                    double nval = script.getExpandedNumber(tokenizer);
                    System.out.print(' ');
                    System.out.println(nval);
                    if (_skip)
                        return;
                    driver.manage().timeouts().implicitlyWait((long) (tokenizer.nval * 1000),
                            TimeUnit.MILLISECONDS);
                    return;
                }

                if (tokenizer.sval.equals("start")) {
                    // HELP: browser start
                    System.out.println();
                    if (null == driver) {
                        // https://sites.google.com/a/chromium.org/chromedriver/capabilities
                        DesiredCapabilities capabilities = DesiredCapabilities.chrome();
                        LoggingPreferences logs = new LoggingPreferences();
                        logs.enable(LogType.BROWSER, Level.ALL);
                        capabilities.setCapability(CapabilityType.LOGGING_PREFS, logs);
                        if (null == options)
                            options = new ChromeOptions();
                        if (null == prefs)
                            prefs = new HashMap<String, Object>();
                        options.setExperimentalOption("prefs", prefs);
                        options.merge(capabilities);
                        driver = new ChromeDriver(options);
                        driver.setLogLevel(Level.ALL);
                        actions = new Actions(driver); // for advanced actions
                    }
                    return;
                }

                if (null == driver) {
                    System.out.println();
                    throw new Exception("browser start must be used before attempt to interract with the browser");
                }

                if (tokenizer.sval.equals("get")) {
                    // HELP: browser get "url"
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') { // expect a quoted string
                        System.out.print(' ');
                        System.out.println(tokenizer.sval);
                        if (_skip)
                            return;
                        if (null == driver)
                            driver = new ChromeDriver(options);
                        driver.get(tokenizer.sval);
                        return;
                    }
                    System.out.println();
                    throw new Exception("browser get command argument should be a quoted url");
                }

                if (tokenizer.sval.equals("refresh")) {
                    // HELP: browser refresh
                    System.out.println();
                    driver.navigate().refresh();
                    return;
                }

                if (tokenizer.sval.equals("back")) {
                    // HELP: browser refresh
                    System.out.println();
                    driver.navigate().back();
                    return;
                }

                if (tokenizer.sval.equals("forward")) {
                    // HELP: browser refresh
                    System.out.println();
                    driver.navigate().forward();
                    return;
                }

                if (tokenizer.sval.equals("close")) {
                    // HELP: browser close
                    System.out.println();
                    if (!_skip) {
                        driver.close();
                        autolog = false;
                    }
                    return;
                }

                if (tokenizer.sval.equals("chrome")) {
                    // HELP: browser chrome <width>,<height>
                    int w = 0, h = 0;
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                        w = (int) tokenizer.nval;
                        System.out.print(' ');
                        System.out.print(w);
                        tokenizer.nextToken();
                        if (tokenizer.ttype == ',') {
                            tokenizer.nextToken();
                            System.out.print(',');
                            if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                                h = (int) tokenizer.nval;
                                System.out.print(h);
                                System.out.println();
                                if (!_skip) {
                                    this.chrome = new Dimension(w, h);
                                }
                                return;
                            }
                        }
                    }
                    throw new Exception("browser chrome arguments error at line " + tokenizer.lineno());
                }

                if (tokenizer.sval.equals("size")) {
                    // HELP: browser size <width>,<height>
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                        final int w = (int) tokenizer.nval;
                        System.out.print(' ');
                        System.out.print(w);
                        tokenizer.nextToken();
                        if (tokenizer.ttype == ',') {
                            tokenizer.nextToken();
                            System.out.print(',');
                            if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                                final int h = (int) tokenizer.nval;
                                System.out.print(h);
                                System.out.println();
                                if (!_skip) {
                                    new WaitFor(cmd, tokenizer, false) {
                                        @Override
                                        protected void run() throws RetryException {
                                            Dimension size = new Dimension(chrome.width + w, chrome.height + h);
                                            System.out.println("// chrome " + chrome.toString());
                                            System.out.println("// size with chrome " + size.toString());
                                            try {
                                                driver.manage().window().setSize(size);
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                                throw new RetryException("Could not set browser size");
                                            }
                                        }
                                    };
                                }
                                return;
                            }
                        }
                    }
                    throw new Exception("browser size arguments error at line " + tokenizer.lineno());
                }
                if (tokenizer.sval.equals("pos")) {
                    // HELP: browser pos <x>,<y>
                    int x = 0, y = 0;
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                        x = (int) tokenizer.nval;
                        System.out.print(' ');
                        System.out.print(x);
                        tokenizer.nextToken();
                        if (tokenizer.ttype == ',') {
                            tokenizer.nextToken();
                            System.out.print(',');
                            if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                                y = (int) tokenizer.nval;
                                System.out.print(y);
                                System.out.println();
                                if (!_skip)
                                    driver.manage().window().setPosition(new Point(x, y));
                                return;
                            }
                        }
                    }
                    throw new Exception("browser size arguments error at line " + tokenizer.lineno());
                }
                throw new Exception("browser unknown command argument at line " + tokenizer.lineno());
            }
            throw new Exception("browser missing command argument at line " + tokenizer.lineno());
        }

        if (cmd.equals("alias") || cmd.equals("function")) {
            // HELP: alias <name> { body }
            // HELP: function <name> (param, ...) { body }
            String name = null, args = null;
            List<String> params = null;
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                System.out.print(' ');
                System.out.print(tokenizer.sval);
                name = tokenizer.sval;
                params = getParams(tokenizer);
                args = getBlock(script, tokenizer, ' ', false);
                System.out.println();
                if (_skip)
                    return;
                addFunction(name, params, args); // add alias
                return;
            }
            System.out.println();
            throw new Exception("alias name expected");
        }

        if (cmd.equals("while")) {
            // HELP: while { block }
            String block = null;
            block = getBlock(script, tokenizer, ' ', false);
            if (_skip)
                return;
            boolean exitloop = false;
            while (!exitloop) {
                try {
                    runString(block, file, "while");
                } catch (Exception e) {
                    exitloop = true;
                }
            }
            return;
        }

        if (cmd.equals("include")) {
            // HELP: include <script>
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                if (_skip)
                    return;
                System.out.print(' ');
                System.out.println(tokenizer.sval);
                File include = new File(tokenizer.sval.startsWith("/") ? tokenizer.sval
                        : file.getParentFile().getCanonicalPath() + "/" + tokenizer.sval);
                runScript(include.getCanonicalPath());
                return;
            }
            throw new Exception("include argument should be a quoted filename");
        }

        if (cmd.equals("exec")) {
            // HELP: exec <command> { args ... }
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String command = tokenizer.sval;
                System.out.print(' ');
                System.out.print(command);
                List<String> args = getArgs(tokenizer, script);
                File include = new File(command.startsWith("/") ? command
                        : file.getParentFile().getCanonicalPath() + "/" + command);
                command = include.getCanonicalPath();
                System.out.println(command);
                List<String> arguments = new ArrayList<String>();
                arguments.add(command);
                arguments.addAll(args);
                Process process = Runtime.getRuntime().exec(arguments.toArray(new String[arguments.size()]));
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line = "";
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
                int exitStatus = process.waitFor();
                if (exitStatus != 0) {
                    throw new Exception("exec command returned failure status " + exitStatus);
                }
                return;
            }
            System.out.println();
            throw new Exception("exec argument should be string or a word");
        }

        if (cmd.equals("exec-include")) {
            // HELP: exec-include <command> { args ... }
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String command = tokenizer.sval;
                System.out.print(' ');
                System.out.print(command);
                List<String> args = getArgs(tokenizer, script);
                File include = new File(command.startsWith("/") ? command
                        : file.getParentFile().getCanonicalPath() + "/" + command);
                command = include.getCanonicalPath();
                System.out.println(command);
                List<String> arguments = new ArrayList<String>();
                arguments.add(command);
                arguments.addAll(args);
                Process process = Runtime.getRuntime().exec(arguments.toArray(new String[arguments.size()]));
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String s = "", line = "";
                while ((line = reader.readLine()) != null) {
                    s += line + "\n";
                }
                int exitStatus = process.waitFor();
                if (exitStatus != 0) {
                    throw new Exception("exec-include command returned failure status " + exitStatus);
                }
                if (s.length() > 0) {
                    runString(s, file, tokenizer.sval);
                }
                return;
            }
            System.out.println();
            throw new Exception(cmd + " argument should be string or a word");
        }

        if (cmd.equals("log")) {
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String action = tokenizer.sval;
                System.out.print(' ');
                System.out.print(action);
                if (action.equals("dump")) {
                    // HELP: log dump
                    System.out.println("");
                    if (driver != null)
                        dumpLog();
                    return;
                }
                if (action.equals("auto")) {
                    // HELP: log auto <on|off>
                    // HELP: log auto <true|false>
                    tokenizer.nextToken();
                    String onoff = tokenizer.sval;
                    System.out.print(' ');
                    System.out.println(onoff);
                    autolog = onoff.equals("on") || onoff.equals("true");
                    return;
                }
                System.out.println();
                throw new Exception("invalid log action");
            }
            System.out.println();
            throw new Exception("log argument should be string or a word");
        }

        if (cmd.equals("default")) {
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String action = tokenizer.sval;
                System.out.print(' ');
                System.out.print(action);
                if (action.equals("wait")) {
                    // HELP: default wait <seconds>
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                        System.out.print(' ');
                        System.out.println(tokenizer.nval);
                        _defaultWaitFor = (int) (tokenizer.nval * 1000.0);
                    }
                    return;
                }
                if (action.equals("screenshot")) {
                    // HELP: default screenshot <path>
                    tokenizer.nextToken();
                    if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                        System.out.print(' ');
                        System.out.println(tokenizer.sval);
                        screenShotPath = tokenizer.sval;
                    }
                    return;
                }
                System.out.println();
                throw new Exception("invalid default property " + tokenizer.sval);
            }
            System.out.println();
            throw new Exception("default argument should be string or a word");
        }

        if (cmd.equals("push")) {
            // HELP: push wait
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String action = tokenizer.sval;
                System.out.print(' ');
                System.out.print(action);
                ArrayList<Object> stack = stacks.get(action);
                if (null == stack) {
                    stack = new ArrayList<Object>();
                    stacks.put(action, stack);
                }
                if (action.equals("wait")) {
                    stack.add(new Long(_waitFor));
                    System.out.println();
                    return;
                }
            }
            System.out.println();
            throw new Error("Invalid push argument");
        }

        if (cmd.equals("pop")) {
            // HELP: pop wait
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String action = tokenizer.sval;
                System.out.print(' ');
                System.out.print(action);
                ArrayList<Object> stack = stacks.get(action);
                if (null == stack || stack.isEmpty()) {
                    throw new Error("pop called without corresponding push");
                }
                if (action.equals("wait")) {
                    int index = stack.size() - 1;
                    _waitFor = (Long) stack.get(index);
                    stack.remove(index);
                    System.out.println();
                    return;
                }
            }
            System.out.println();
            throw new Error("Invalid push argument");
        }

        if (cmd.equals("echo")) {
            // HELP: echo "string"
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String text = script.getExpandedString(tokenizer);
                System.out.print(' ');
                System.out.println(text);
                if (!_skip)
                    System.out.println(text);
                return;
            }
            System.out.println();
            throw new Exception("echo argument should be string or a word");
        }

        if (cmd.equals("sleep")) {
            // HELP: sleep <seconds>
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                System.out.print(' ');
                System.out.println(tokenizer.nval);
                this.sleep((long) (tokenizer.nval * 1000));
                return;
            }
            System.out.println();
            throw new Exception("sleep command argument should be a number");
        }

        if (cmd.equals("fail")) {
            // HELP: fail "<message>"
            tokenizer.nextToken();
            if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                String text = tokenizer.sval;
                System.out.print(' ');
                System.out.println(text);
                if (!_skip) {
                    System.out.println("TEST FAIL: " + text);
                    throw new Exception(text);
                }
                return;
            }
            System.out.println();
            throw new Exception("echo argument should be string or a word");
        }

        if (cmd.equals("debugger")) {
            // HELP: debugger
            System.out.println();
            this.sleepSeconds(10);
            return;
        }

        if (cmd.equals("if")) {
            // HELP: if <commands> then <commands> [else <commands>] endif
            _if = true;
            System.out.println();
            return;
        }

        if (cmd.equals("then")) {
            _if = false;
            _skip = !_test;
            System.out.println();
            return;
        }

        if (cmd.equals("else")) {
            _if = false;
            _skip = _test;
            System.out.println();
            return;
        }

        if (cmd.equals("endif")) {
            _skip = false;
            System.out.println();
            return;
        }

        if (cmd.equals("not")) {
            // HELP: not <check-command>
            System.out.println();
            _not = true;
            return;
        }

        if (null != driver) {

            // all these command require the browser to have been started

            if (cmd.equals("field") || cmd.equals("id") || cmd.equals("test-id")) {
                // HELP: field "<test-id>"
                // HELP: id "<test-id>"
                // HELP: test-id "<test-id>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    if (_skip)
                        return;
                    System.out.print(' ');
                    this.setContextToField(script, tokenizer);
                    return;
                }
                System.out.println();
                throw new Exception(cmd + " command requires a form.field argument");
            }

            if (cmd.equals("select")) {
                // HELP: select "<query-selector>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    if (_skip)
                        return;
                    System.out.print(' ');
                    selectContext(tokenizer, script);
                    return;
                }
                System.out.println();
                throw new Exception(cmd + " command requires a css selector argument");
            }

            if (cmd.equals("xpath")) {
                // HELP: xpath "<xpath-expression>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    if (_skip)
                        return;
                    System.out.print(' ');
                    this.xpathContext(script, tokenizer);
                    return;
                }
                System.out.println();
                throw new Exception(cmd + " command requires a css selector argument");
            }

            if (cmd.equals("wait")) {
                // HELP: wait <seconds>
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                    System.out.print(' ');
                    System.out.println(tokenizer.nval);

                    // we will repeat then next select type command until it succeeds or we timeout
                    _waitFor = (long) ((new Date()).getTime() + (tokenizer.nval * 1000));
                    return;
                }

                // HELP: wait <action>
                if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
                    String action = tokenizer.sval;
                    System.out.println(' ');
                    System.out.println(action);
                    if (action.equals("clickable")) {
                        long sleep = (_waitFor - (new Date()).getTime()) / 1000;
                        if (sleep > 0) {
                            System.out.println("WebDriverWait for " + sleep + " seconds");
                            WebDriverWait wait = new WebDriverWait(driver, sleep);
                            WebElement element = wait.until(ExpectedConditions.elementToBeClickable(selection));
                            if (element != selection) {
                                throw new Exception("element is not clickable");
                            }
                        } else {
                            System.out.println("WebDriverWait for " + sleep + " seconds (skipped)");
                        }
                        return;
                    }
                }
                throw new Exception(cmd + " command requires a seconds argument");
            }

            if (cmd.equals("set") || cmd.equals("send")) {
                // HELP: set "<value>"
                // HELP: send "<value>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    if (_skip)
                        return;
                    System.out.print(' ');
                    System.out.println(script.getExpandedString(tokenizer));
                    this.setContextValue(cmd, script, tokenizer, cmd.equals("set"));
                    return;
                }
                System.out.println();
                throw new Exception("set command requires a value argument");
            }

            if (cmd.equals("test") || cmd.equals("check")) {
                // HELP: test "<value>"
                // HELP: check "<value>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"'
                        || tokenizer.ttype == '\'') {
                    if (_skip)
                        return;
                    System.out.print(' ');
                    System.out.println(script.getExpandedString(tokenizer));
                    this.testContextValue(cmd, script, tokenizer, false);
                    return;
                }
                System.out.println();
                throw new Exception(cmd + " command requires a value argument");
            }

            if (cmd.equals("checksum")) {
                // HELP: checksum "<checksum>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"'
                        || tokenizer.ttype == '\'') {
                    if (_skip)
                        return;
                    System.out.print(' ');
                    System.out.println(script.getExpandedString(tokenizer));
                    this.testContextValue(cmd, script, tokenizer, true);
                    return;
                }
                System.out.println();
                throw new Exception(cmd + " command requires a value argument");
            }

            if (cmd.equals("click") || cmd.equals("click-now")) {
                // HELP: click
                System.out.println();
                final boolean wait = !cmd.equals("click-now");
                new WaitFor(cmd, tokenizer, true) {
                    @Override
                    protected void run() throws RetryException {
                        if (!_skip) {
                            if (wait) {
                                long sleep = (_waitFor - (new Date()).getTime()) / 1000;
                                if (sleep > 0) {
                                    System.out.println("WebDriverWait for " + sleep + " seconds");
                                    WebDriverWait wait = new WebDriverWait(driver, sleep);
                                    WebElement element = wait
                                            .until(ExpectedConditions.elementToBeClickable(selection));
                                    if (element == selection) {
                                        selection.click();
                                        return;
                                    } else {
                                        throw new RetryException("click failed");
                                    }
                                }
                            }
                            // click-nowait, no or negative wait period, just click
                            selection.click();
                        }
                    }
                };
                return;
            }

            if (cmd.equals("scroll-into-view")) {
                // HELP: scroll-into-view
                System.out.println();
                if (null == selection)
                    throw new Exception(cmd + " command requires a field selection at line " + tokenizer.lineno());
                if (!_skip) {
                    try {
                        scrollContextIntoView(selection);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                        info(selection, selectionCommand, false);
                        throw e;
                    }
                }
                return;
            }

            if (cmd.equals("clear")) {
                // HELP: clear
                System.out.println();
                new WaitFor(cmd, tokenizer, true) {
                    @Override
                    protected void run() {
                        if (!_skip)
                            selection.clear();
                    }
                };
                return;
            }

            if (cmd.equals("call")) {
                // HELP: call <function> { args ... }
                String function = null, args = null;
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') { // expect a quoted string
                    function = script.getExpandedString(tokenizer);
                    System.out.print(' ');
                    System.out.print(function);
                    args = getBlock(script, tokenizer, ',', true);
                    System.out.println();
                    if (_skip)
                        return;
                    if (null == args)
                        args = "";
                    String js = "var result = window.RegressionTest.test('" + function + "',[" + args + "]);"
                            + "arguments[arguments.length-1](result);";
                    System.out.println("> " + js);
                    Object result = driver.executeAsyncScript(js);
                    if (null != result) {
                        if (result.getClass() == RemoteWebElement.class) {
                            selection = (RemoteWebElement) result;
                            stype = SelectionType.Script;
                            selector = js;
                            System.out.println("new selection " + selection);
                        }
                    }
                    return;
                }
                System.out.println();
                throw new Exception("missing arguments for call statement at line " + tokenizer.lineno());
            }

            if (cmd.equals("enabled")) {
                // HELP: enabled
                System.out.println();
                new WaitFor(cmd, tokenizer, true) {
                    @Override
                    protected void run() throws RetryException {
                        if (!_skip) {
                            if (selection.isEnabled() != _not) {
                                _not = false;
                                return;
                            }
                            throw new RetryException("enabled check failed");
                        }
                    }
                };
                return;
            }

            if (cmd.equals("selected")) {
                // HELP: selected
                System.out.println();
                new WaitFor(cmd, tokenizer, true) {
                    @Override
                    protected void run() throws RetryException {
                        if (!_skip) {
                            if (selection.isSelected() != _not) {
                                _not = false;
                                return;
                            }
                            throw new RetryException("selected check failed");
                        }
                    }
                };
                return;
            }

            if (cmd.equals("displayed")) {
                // HELP: displayed
                System.out.println();
                new WaitFor(cmd, tokenizer, true) {
                    @Override
                    protected void run() throws RetryException {
                        if (!_skip) {
                            if (selection.isDisplayed() != _not) {
                                _not = false;
                                return;
                            }
                            throw new RetryException("displayed check failed");
                        }
                    }
                };
                return;
            }

            if (cmd.equals("at")) {
                // HELP: at <x|*>,<y>
                int x = 0, y = 0;
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_NUMBER || tokenizer.ttype == '*') {
                    x = (int) tokenizer.nval;
                    System.out.print(' ');
                    if (tokenizer.ttype == '*') {
                        x = -1;
                        System.out.print('*');
                    } else {
                        x = (int) tokenizer.nval;
                        System.out.print(x);
                    }
                    tokenizer.nextToken();
                    if (tokenizer.ttype == ',') {
                        tokenizer.nextToken();
                        System.out.print(',');
                        if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                            y = (int) tokenizer.nval;
                            System.out.print(y);
                            System.out.println();
                            final int X = x;
                            final int Y = y;
                            new WaitFor(cmd, tokenizer, true) {
                                @Override
                                protected void run() throws RetryException {
                                    if (!_skip) {
                                        Point loc = selection.getLocation();
                                        if (((loc.x == X || X == -1) && loc.y == Y) != _not) {
                                            _not = false;
                                            return;
                                        }
                                        throw new RetryException("location check failed");
                                    }
                                }
                            };
                            return;
                        }
                    }
                }
                System.out.println();
                throw new Exception("at missing co-ordiantes at line " + tokenizer.lineno());
            }

            if (cmd.equals("size")) {
                // HELP: size <w|*>,<h>
                int mw = 0, w = 0, h = 0;
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_NUMBER || tokenizer.ttype == '*') {
                    System.out.print(' ');
                    if (tokenizer.ttype == '*') {
                        mw = w = -1;
                        System.out.print('*');
                    } else {
                        mw = w = (int) tokenizer.nval;
                        System.out.print(w);
                    }
                    tokenizer.nextToken();
                    if (tokenizer.ttype == ':') {
                        tokenizer.nextToken();
                        w = (int) tokenizer.nval;
                        System.out.print(':');
                        System.out.print(w);
                        tokenizer.nextToken();
                    }
                    if (tokenizer.ttype == ',') {
                        tokenizer.nextToken();
                        System.out.print(',');
                        if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
                            h = (int) tokenizer.nval;
                            System.out.print(h);
                            System.out.println();
                            final int MW = mw;
                            final int W = w;
                            final int H = h;
                            new WaitFor(cmd, tokenizer, true) {
                                @Override
                                protected void run() throws RetryException {
                                    if (!_skip) {
                                        Dimension size = selection.getSize();
                                        if (((MW == -1 || (size.width >= MW && size.width <= W))
                                                && size.height == H) != _not) {
                                            _not = false;
                                            return;
                                        }
                                        throw new RetryException("size check failed");
                                    }
                                }
                            };
                            return;
                        }
                    }
                }
                System.out.println();
                throw new Exception("size missing dimensions at line " + tokenizer.lineno());
            }

            if (cmd.equals("tag")) {
                // HELP: tag <tag-name>
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    System.out.print(' ');
                    System.out.print(tokenizer.sval);
                    System.out.println();
                    new WaitFor(cmd, tokenizer, true) {
                        @Override
                        protected void run() throws RetryException {
                            if (!_skip) {
                                String tag = selection.getTagName();
                                if (tokenizer.sval.equals(tag) != _not) {
                                    _not = false;
                                    return;
                                }
                                throw new RetryException("tag \"" + tokenizer.sval + "\" check failed, tag is "
                                        + tag + " at line " + tokenizer.lineno());
                            }
                        }
                    };
                    return;
                }
                System.out.println();
                throw new Exception("tag command has missing tag name at line " + tokenizer.lineno());
            }

            if (cmd.equals("info")) {
                // HELP: info
                System.out.println();
                if (null == selection)
                    throw new Exception("info command requires a selection at line " + tokenizer.lineno());
                info(selection, selectionCommand, true);
                return;
            }

            if (cmd.equals("alert")) {
                // HELP: alert accept
                System.out.println();
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    System.out.print(' ');
                    System.out.print(tokenizer.sval);
                    if (tokenizer.sval.equals("accept")) {
                        System.out.println();
                        if (!_skip)
                            driver.switchTo().alert().accept();
                        return;
                    }
                }
                System.out.println();
                throw new Exception("alert syntax error at line " + tokenizer.lineno());
            }

            if (cmd.equals("dump")) {
                // HELP: dump
                System.out.println();
                if (!_skip)
                    dump();
                return;
            }

            if (cmd.equals("mouse")) {
                // HELP: mouse { <center|0,0|origin|body|down|up|click|+/-x,+/-y> commands ... }
                parseBlock(script, tokenizer, new BlockHandler() {
                    public void parseToken(StreamTokenizer tokenizer, String token) {
                        int l = token.length();
                        if (token.equals("center")) {
                            actions.moveToElement(selection);
                        } else if ((l > 1 && token.substring(1, l - 1).equals("0,0")) || token.equals("origin")) {
                            actions.moveToElement(selection, 0, 0);
                        } else if (token.equals("body")) {
                            actions.moveToElement(driver.findElement(By.tagName("body")), 0, 0);
                        } else if (token.equals("down")) {
                            actions.clickAndHold();
                        } else if (token.equals("up")) {
                            actions.release();
                        } else if (token.equals("click")) {
                            actions.click();
                        } else if (l > 1) {
                            String[] a = token.substring(1, l - 1).split(",");
                            actions.moveByOffset(Integer.valueOf(a[0]), Integer.valueOf(a[1]));
                        } else {
                            // no-op
                        }
                    }
                });
                System.out.println();
                actions.release();
                actions.build().perform();
                return;
            }

            if (cmd.equals("screenshot")) {
                // HELP: screenshot "<path>"
                tokenizer.nextToken();
                if (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '"') {
                    String path = tokenizer.sval;
                    System.out.print(' ');
                    System.out.println(path);
                    if (!_skip) {
                        WebDriver augmentedDriver = new Augmenter().augment(driver);
                        File screenshot = ((TakesScreenshot) augmentedDriver).getScreenshotAs(OutputType.FILE);
                        String outputPath;
                        if (screenShotPath == null || path.startsWith("/") || path.substring(1, 1).equals(":")) {
                            outputPath = path;
                        } else {
                            outputPath = screenShotPath + (screenShotPath.endsWith("/") ? "" : "/") + path;
                        }
                        System.out.println(screenshot.getAbsolutePath() + " -> " + path);
                        FileUtils.moveFile(screenshot, new File(outputPath));
                    }
                    return;
                }
                System.out.println();
                throw new Exception("screenshot argument should be a path");
            }
        }

        if (functions.containsKey(cmd)) {
            executeFunction(cmd, file, tokenizer, script);
            return;
        }

        if (null == driver) {
            throw new Exception("browser start must be used before attempt to interract with the browser");
        }

        System.out.println();
        throw new Exception("unrecognised command, " + cmd);
    }

    private void dumpLog() throws Exception {
        Logs log = driver.manage().logs();
        LogEntries entries = log.get(LogType.BROWSER);
        // System.out.println(entries);
        List<LogEntry> list = entries.getAll();
        boolean fail = false;
        for (int i = 0; i < list.size(); i++) {
            LogEntry e = list.get(i);
            System.out.println(e);
            if (e.getLevel().getName().equals("SEVERE") && e.getMessage().indexOf("Uncaught ") != -1
                    && e.getMessage().indexOf(" Error:") != -1) {
                System.out.println("*** Uncaught Error ***");
                fail = true;
            }
        }
        if (fail)
            throw new Exception("Unhandled Exception! Check console log for details");
    }

    private void info(WebElement element, String selector, boolean verify) throws Exception {
        do {
            try {
                Point loc = element.getLocation();
                Dimension size = element.getSize();
                String tag = element.getTagName();
                System.out
                        .print(null == selector ? "test-id \"" + element.getAttribute("test-id") + "\"" : selector);
                System.out.print(" info");
                System.out.print(" tag " + tag);
                System.out.print((element.isDisplayed() ? "" : " not") + " displayed");
                System.out.print(" at " + loc.x + "," + loc.y);
                System.out.print(" size " + size.width + "," + size.height);
                System.out.print((element.isEnabled() ? "" : " not") + " enabled");
                System.out.print((element.isSelected() ? "" : " not") + " selected");
                if (tag.equals("input") || tag.equals("select")) {
                    System.out.print(" check \"" + element.getAttribute("value") + "\"");
                } else {
                    String text = tag.equals("textarea") ? element.getAttribute("value") : element.getText();
                    if (text.indexOf('\n') != -1) {
                        CRC32 crc = new CRC32();
                        crc.update(text.getBytes());
                        System.out.print(" checksum \"crc32:" + crc.getValue() + "\"");
                    } else {
                        System.out.print(" check \"" + element.getText() + "\"");
                    }
                }
                System.out.println();
                return;
            } catch (StaleElementReferenceException e) {
                // If element has gone stale during a dump, ignore it
                if (!verify)
                    return;
                // element has gone stale, re-select it
                System.out.println("// EXCEPTION : StaleElementReference");
            } catch (Exception e) {
                if (verify)
                    throw e;
                return;
            }
            sleepAndReselect(100);
        } while (_waitFor > 0 && (new Date()).getTime() < _waitFor);
    }

    // Find all test-id fields, and dump their info
    private void dump() throws Exception {
        List<WebElement> elements = driver.findElements(By.xpath("//*[@test-id]"));
        for (int i = 0; i < elements.size(); i++) {
            info(elements.get(i), null, false);
        }
    }

    private void addFunction(String name, List<String> params, String args) {
        ExecutionContext script = new ExecutionContext(params, args);
        functions.put(name, script);
    }

    private boolean compareStrings(String s1, String s2, boolean checksum) {
        if (checksum) {
            CRC32 crc = new CRC32();
            crc.update(s1.getBytes());
            return ("crc32:" + crc.getValue()).equals(s2);
        }
        return s1.equals(s2);
    }

    private void testContextValue(String cmd, final ExecutionContext script, final StreamTokenizer tokenizer,
            final boolean checksum) throws Exception {
        new WaitFor(cmd, tokenizer, true) {
            @Override
            protected void run() throws RetryException {
                String tagName = selection.getTagName();
                String test = script.getExpandedString(tokenizer);
                if (tagName.equals("input") || tagName.equals("select") || tagName.equals("textarea")) {
                    System.out.println(
                            "// Checking element value is " + (_not ? "NOT " : "") + " equal to '" + test + "'");
                    String value = selection.getAttribute("value");
                    if (_not != (null != value && compareStrings(value, test, checksum))) {
                        _not = false;
                        return;
                    }
                    if (null == value) {
                        System.out.println("// CHECK FAIL: EXPECTED '" + test + "' BUT VALUE IS NULL");
                    } else {
                        System.out.println("// CHECK FAIL: EXPECTED '" + test + "' WHICH DOES "
                                + (_not ? "" : "NOT ") + " MATCH '" + value + "'");
                    }
                    throw new RetryException("value check failed");
                } else {
                    System.out.println("// Checking element textContent is " + (_not ? "NOT " : "") + "equal to '"
                            + test + "'");
                    String value = selection.getText();
                    System.out.println("text: " + value);
                    if (_not != (null != value && compareStrings(value, test, checksum))) {
                        _not = false;
                        return;
                    }
                    if (null == value) {
                        System.out.println("// CHECK FAIL: EXPECTED '" + test + "' BUT VALUE IS NULL");
                    } else {
                        System.out.println("// CHECK FAIL: EXPECTED '" + test + "' WHICH DOES "
                                + (_not ? "" : "NOT ") + " MATCH '" + value + "'");
                    }
                    throw new RetryException("textContent check failed");
                }
            }

            @Override
            protected void fail(Exception e) throws Exception {
                throw new Exception(e.getMessage() + ": " + tokenizer.sval
                        + " test failed for current select selection at line " + tokenizer.lineno());
            }
        };
    }

    private void setContextValue(String cmd, final ExecutionContext script, final StreamTokenizer tokenizer,
            final boolean set) throws Exception {
        new WaitFor(cmd, tokenizer, true) {
            @Override
            protected void run() throws Exception {
                final String tagName = selection.getTagName();
                if (tagName.equals("input") || tagName.equals("select") || tagName.equals("textarea")) {
                    if (set && !tagName.equals("select"))
                        selection.clear();
                    selection.sendKeys(script.getExpandedString(tokenizer));
                } else {
                    throw new Exception(
                            "set cannot be used on a non-field selection at line " + tokenizer.lineno());
                }
            }

            @Override
            protected void fail(Exception e) throws Exception {
                throw new Exception("could not send keys to element: " + e.getMessage());
            }
        };
    }

    private void sleepSeconds(long seconds) {
        try {
            Sleeper.SYSTEM_SLEEPER.sleep(Duration.ofSeconds(seconds));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void sleep(long ms) {
        try {
            Sleeper.SYSTEM_SLEEPER.sleep(Duration.ofMillis(ms));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private boolean sleepAndReselect(int ms) throws Exception {
        if (autolog)
            dumpLog();
        long waitTimer = (_waitFor - (new Date()).getTime());
        System.out.println("// SLEEP AND RESELECT [wait=" + waitTimer + "] ID " + selection.getId());
        //      if (waitTimer < -(ms*2)) {
        //         System.out.println("AUTO WAIT FOR 1s");
        //         _waitFor = (new Date()).getTime() + 1000;
        //      }
        this.sleep(ms);
        try {
            if (stype == SelectionType.XPath || stype == SelectionType.Field) {
                selection = (RemoteWebElement) driver.findElement(By.xpath(selector));
            } else if (stype == SelectionType.Select) {
                selection = (RemoteWebElement) driver.findElement(By.cssSelector(selector));
            } else if (stype == SelectionType.Script) {
                Object result = driver.executeAsyncScript(selector);
                if (null != result) {
                    if (result.getClass() == RemoteWebElement.class) {
                        selection = (RemoteWebElement) result;
                    }
                }
            }
        } catch (NoSuchElementException e2) {
            // element has gone stale, re-select it
            System.out.println("// SLEEPANDRESELECT: EXCEPTION : NoSuchElement");
        } catch (Exception e) {
            System.out.println("// SLEEPANDRESELECT: EXCEPTION : " + e.getMessage());
            throw e;
        }
        System.out.println("// SLEEPANDRESELECT: RESELECTED " + selector + " SLEEP 100ms");
        this.sleep(100); // small delay
        return true;
    }

    private void xpathContext(ExecutionContext script, StreamTokenizer tokenizer) throws Exception {
        Exception e;
        stype = SelectionType.None;
        selector = null;
        String sval = script.getExpandedString(tokenizer);
        System.out.println(sval);
        do {
            try {
                selection = (RemoteWebElement) driver.findElement(By.xpath(sval));
                if (_not) {
                    _test = _not = false;
                    throw new Exception("not xpath " + sval + " is invalid on line " + tokenizer.lineno());
                }
                selectionCommand = "xpath \"" + sval + "\"";
                stype = SelectionType.XPath;
                selector = sval;
                _test = true;
                return;
            } catch (Exception ex) {
                e = ex;
                sleep(100);
            }
        } while (_waitFor > 0 && (new Date()).getTime() < _waitFor);
        _waitFor = 0;
        _test = false;
        if (!_if) {
            if (_not) {
                _test = true;
                selection = null;
                _not = false;
                return;
            }
            throw new Exception(
                    "xpath " + sval + " is invalid on line " + tokenizer.lineno() + " " + e.getMessage());
        }
    }

    private void selectContext(StreamTokenizer tokenizer, ExecutionContext script) throws Exception {
        Exception e;
        stype = SelectionType.None;
        selector = null;
        String sval = script.getExpandedString(tokenizer);
        System.out.println(sval);
        do {
            try {
                selection = (RemoteWebElement) driver.findElement(By.cssSelector(sval));
                if (_not) {
                    _test = _not = false;
                    throw new Exception("not selector " + sval + " is invalid on line " + tokenizer.lineno());
                }
                selectionCommand = "select \"" + sval + "\"";
                stype = SelectionType.Select;
                selector = sval;
                _test = true;
                return;
            } catch (Exception ex) {
                e = ex;
                sleep(100);
            }
        } while (_waitFor > 0 && (new Date()).getTime() < _waitFor);
        _waitFor = 0;
        _test = false;
        if (!_if) {
            if (_not) {
                _test = true;
                selection = null;
                selector = null;
                _not = false;
                return;
            }
            throw new Exception(
                    "selector " + sval + " is invalid on line " + tokenizer.lineno() + " " + e.getMessage());
        }
    }

    private void setContextToField(ExecutionContext script, StreamTokenizer tokenizer) throws Exception {
        Exception e;
        String sval = script.getExpandedString(tokenizer);
        System.out.println(sval);
        String query = "//*[@test-id='" + sval + "']";
        stype = SelectionType.None;
        selector = null;
        do {
            try {
                selection = (RemoteWebElement) driver.findElement(By.xpath(query));
                if (_not) {
                    _test = _not = false;
                    throw new Exception("not test-id " + sval + " is invalid on line " + tokenizer.lineno());
                }
                selectionCommand = "field \"" + sval + "\"";
                stype = SelectionType.Field;
                selector = query;
                _test = true;
                return;
            } catch (Exception ex) {
                e = ex;
                sleep(100);
            }
        } while (this._waitFor > 0 && (new Date()).getTime() < this._waitFor);
        _waitFor = 0;
        _test = false;
        if (!_if) {
            if (_not) {
                _test = true;
                selection = null;
                _not = false;
                return;
            }
            throw new Exception(
                    "field reference " + sval + " is invalid on line " + tokenizer.lineno() + " " + e.getMessage());
        }
    }

    private void scrollContextIntoView(WebElement element) throws Exception {
        Capabilities cp = ((RemoteWebDriver) driver).getCapabilities();
        if (cp.getBrowserName().equals("chrome")) {
            try {
                ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", element);
            } catch (Exception e) {
                throw e;
            }
        }
    }
}