package jp.vmi.selenium.selenese.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.ParserProperties;
import jp.vmi.selenium.selenese.result.Result.Level;
import jp.vmi.selenium.selenese.utils.LangUtils;
import jp.vmi.selenium.selenese.utils.SystemInformation;
import static jp.vmi.selenium.selenese.result.Result.Level.*;
/**
* Configuration information.
*/
@SuppressWarnings("javadoc")
public class DefaultConfig implements IConfig {
private static final int HELP_WIDTH = 80;
private static final int HELP_PADDING = 2;
// default values.
public static final int DEFAULT_TIMEOUT_MILLISEC_N = 30000;
public static final String DEFAULT_TIMEOUT_MILLISEC = Integer.toString(DEFAULT_TIMEOUT_MILLISEC_N);
// parts of help message.
private static final String[] HEADER = {
"Selenese script interpreter implemented by Java.",
"",
"Usage: java -jar selenese-runner.jar <option> ... <test-case|test-suite> ..."
};
private static String statusListItem(Level level) {
return "- " + level.strictExitCode + ": " + level.name();
}
private static final String[] FOOTER = {
"[Note]",
"*1 It is available if using \"--driver remote --remote-browser firefox\".",
"",
"*2 If you want to use basic and/or proxy authentication on Firefox, "
+ "then create new profile, "
+ "install AutoAuth plugin, "
+ "configure all settings, "
+ "access test site with the profile, "
+ "and specify the profile by --profile option.",
"",
"*3 Use \"java -cp ..." + File.pathSeparator + "selenese-runner.jar Main --command-factory ...\". ",
"Because \"java\" command ignores all class path settings, when using \"-jar\" option.",
"",
"*4 The list of strict exit code is follows:",
statusListItem(SUCCESS),
statusListItem(WARNING),
statusListItem(FAILURE),
statusListItem(ERROR),
statusListItem(UNEXECUTED),
statusListItem(MAX_TIME_EXCEEDED)
};
private final CmdLineParser parser;
private final int helpWidth;
private IConfig parentOptions = null;
@Option(name = "--config", aliases = "-c", metaVar = "<file>", usage = "load option information from file.")
private String config;
@Option(name = "--driver", aliases = "-d", metaVar = "<driver>",
usage = "firefox (default) | chrome | ie | safari | htmlunit | phantomjs | remote | appium | FQCN-of-WebDriverFactory")
private String driver;
@Option(name = "--profile", aliases = "-p", metaVar = "<name>", usage = "profile name (Firefox only *1)")
private String profile;
@Option(name = "--profile-dir", aliases = "-P", metaVar = "<dir>", usage = "profile directory (Firefox only *1)")
private String profileDir;
@Option(name = "--chrome-experimental-options", metaVar = "<file>", usage = "path to json file specify experimental options for chrome (Chrome only *1)")
private String chromeExperimentalOptions;
@Option(name = "--chrome-extension", metaVar = "<file>", usage = "chrome extension file (multiple, Chrome only *1)")
private String[] chromeExtension;
@Option(name = "--proxy", metaVar = "<proxy>", usage = "proxy host and port (HOST:PORT) (excepting IE)")
private String proxy;
@Option(name = "--proxy-user", metaVar = "<user>", usage = "proxy username (HtmlUnit only *2)")
private String proxyUser;
@Option(name = "--proxy-password", metaVar = "<password>", usage = "proxy password (HtmlUnit only *2)")
private String proxyPassword;
@Option(name = "--no-proxy", metaVar = "<hosts>", usage = "no-proxy hosts")
private String noProxy;
@Option(name = "--cli-args", metaVar = "<arg>", usage = "add command line arguments at starting up driver (multiple)")
private String[] cliArgs;
@Option(name = "--remote-url", metaVar = "<url>", usage = "Remote test runner URL (Remote only)")
private String remoteUrl;
@Option(name = "--remote-platform", metaVar = "<platform>", usage = "Desired remote platform (Remote only)")
private String remotePlatform;
@Option(name = "--remote-browser", metaVar = "<browser>", usage = "Desired remote browser (Remote only)")
private String remoteBrowser;
@Option(name = "--remote-version", metaVar = "<browser-version>", usage = "Desired remote browser version (Remote only)")
private String remoteVersion;
@Option(name = "--highlight", aliases = "-H", usage = "highlight locator always.")
private Boolean highlight;
@Option(name = "--interactive", aliases = "-i", usage = "interactive mode.")
private Boolean interactive;
@Option(name = "--screenshot-dir", aliases = "-s", metaVar = "<dir>", usage = "override captureEntirePageScreenshot directory.")
private String screenshotDir;
@Option(name = "--screenshot-all", aliases = "-S", metaVar = "<dir>", usage = "take screenshot at all commands to specified directory.")
private String screenshotAll;
@Option(name = "--screenshot-on-fail", metaVar = "<dir>", usage = "take screenshot on fail commands to specified directory.")
private String screenshotOnFail;
@Option(name = "--ignore-screenshot-command", usage = "ignore captureEntirePageScreenshot command.")
private Boolean ignoreScreenshotCommand;
@Option(name = "--baseurl", aliases = "-b", metaVar = "<baseURL>", usage = "override base URL set in selenese.")
private String baseurl;
@Option(name = "--firefox", metaVar = "<path>", usage = "path to 'firefox' binary. (implies '--driver firefox')")
private String firefox;
@Option(name = "--geckodriver", metaVar = "<path>", usage = "path to 'geckodriver' binary. (implies '--driver firefox')")
private String geckodriver;
@Option(name = "--chromedriver", metaVar = "<path>", usage = "path to 'chromedriver' binary. (implies '--driver chrome')")
private String chromedriver;
@Option(name = "--iedriver", metaVar = "<path>", usage = "path to 'IEDriverServer' binary. (implies '--driver ie')")
private String iedriver;
@Option(name = "--phantomjs", metaVar = "<path>", usage = "path to 'phantomjs' binary. (implies '--driver phantomjs')")
private String phantomjs;
@Option(name = "--xml-result", metaVar = "<dir>", usage = "output XML JUnit results to specified directory.")
private String xmlResult;
@Option(name = "--html-result", metaVar = "<dir>", usage = "output HTML results to specified directory.")
private String htmlResult;
@Option(name = "--timeout", aliases = "-t", metaVar = "<timeout>", usage = "set timeout (ms) for waiting. (default: " + DEFAULT_TIMEOUT_MILLISEC_N + " ms)")
private String timeout;
@Option(name = "--set-speed", metaVar = "<speed>", usage = "same as executing setSpeed(ms) command first.")
private String setSpeed;
@Option(name = "--height", metaVar = "<height>", usage = "set initial height. (excluding mobile)")
private String height;
@Option(name = "--width", metaVar = "<width>", usage = "set initial width. (excluding mobile)")
private String width;
@Option(name = "--define", aliases = "-D", metaVar = "<key>[:<type>][+]=<value>",
usage = "define parameters for capabilities. <type> is a value type: str (default), int or bool (multiple)")
private String[] define;
@Option(name = "--rollup", metaVar = "<file>", usage = "define rollup rule by JavaScript. (multiple)")
private String[] rollup;
@Option(name = "--cookie-filter", metaVar = "<+RE|-RE>", usage = "filter cookies to log by RE matching the name. (\"+\" is passing, \"-\" is ignoring)")
private String cookieFilter;
@Option(name = "--command-factory", metaVar = "<FQCN>", usage = "register user defined command factory. (See Note *3)")
private String commandFactory;
@Option(name = "--no-exit", usage = "don't call System.exit at end.")
private Boolean noExit;
@Option(name = "--strict-exit-code", usage = "return strict exit code, reflected by selenese command results at end. (See Note *4)")
private Boolean strictExitCode;
@Option(name = "--max-time", metaVar = "<max-time>", usage = "Maximum time in seconds that you allow the entire operation to take.")
private String maxTime;
@Option(name = "--help", aliases = "-h", usage = "show this message.")
private Boolean help;
@Argument
private String[] args = LangUtils.EMPTY_STRING_ARRAY;
private final OptionMap optionMap = new OptionMap(this);
/**
* Constructor.
*
* @param args command line arguments.
*/
public DefaultConfig(String... args) {
String columns = System.getProperty("columns", System.getenv("COLUMNS"));
helpWidth = (columns != null && columns.matches("\\d+")) ? Integer.parseInt(columns) : HELP_WIDTH;
ParserProperties props = ParserProperties.defaults()
.withOptionSorter(null)
.withUsageWidth(helpWidth)
.withShowDefaults(false);
parser = new CmdLineParser(this, props);
if (args.length > 0)
parseCommandLine(args);
}
@Override
public String getDriver() {
return driver != null ? driver : (parentOptions != null ? parentOptions.getDriver() : null);
}
public void setDriver(String driver) {
this.driver = driver;
}
@Override
public String getProfile() {
return profile != null ? profile : (parentOptions != null ? parentOptions.getProfile() : null);
}
public void setProfile(String profile) {
this.profile = profile;
}
@Override
public String getProfileDir() {
return profileDir != null ? profileDir : (parentOptions != null ? parentOptions.getProfileDir() : null);
}
public void setProfileDir(String profileDir) {
this.profileDir = profileDir;
}
@Override
public String getChromeExperimentalOptions() {
return chromeExperimentalOptions != null ? chromeExperimentalOptions : (parentOptions != null ? parentOptions.getChromeExperimentalOptions() : null);
}
public void setChromeExperimentalOptions(String chromeExperimentalOptions) {
this.chromeExperimentalOptions = chromeExperimentalOptions;
}
@Override
public String[] getChromeExtension() {
return chromeExtension != null ? chromeExtension : (parentOptions != null ? parentOptions.getChromeExtension() : null);
}
public void addChromeExtension(String chromeExtensionItem) {
this.chromeExtension = ArrayUtils.add(this.chromeExtension, chromeExtensionItem);
}
@Override
public String getProxy() {
return proxy != null ? proxy : (parentOptions != null ? parentOptions.getProxy() : null);
}
public void setProxy(String proxy) {
this.proxy = proxy;
}
@Override
public String getProxyUser() {
return proxyUser != null ? proxyUser : (parentOptions != null ? parentOptions.getProxyUser() : null);
}
public void setProxyUser(String proxyUser) {
this.proxyUser = proxyUser;
}
@Override
public String getProxyPassword() {
return proxyPassword != null ? proxyPassword : (parentOptions != null ? parentOptions.getProxyPassword() : null);
}
public void setProxyPassword(String proxyPassword) {
this.proxyPassword = proxyPassword;
}
@Override
public String getNoProxy() {
return noProxy != null ? noProxy : (parentOptions != null ? parentOptions.getNoProxy() : null);
}
public void setNoProxy(String noProxy) {
this.noProxy = noProxy;
}
@Override
public String[] getCliArgs() {
return cliArgs != null ? cliArgs : (parentOptions != null ? parentOptions.getCliArgs() : null);
}
public void addCliArgs(String cliArgsItem) {
this.cliArgs = ArrayUtils.add(this.cliArgs, cliArgsItem);
}
@Override
public String getRemoteUrl() {
return remoteUrl != null ? remoteUrl : (parentOptions != null ? parentOptions.getRemoteUrl() : null);
}
public void setRemoteUrl(String remoteUrl) {
this.remoteUrl = remoteUrl;
}
@Override
public String getRemotePlatform() {
return remotePlatform != null ? remotePlatform : (parentOptions != null ? parentOptions.getRemotePlatform() : null);
}
public void setRemotePlatform(String remotePlatform) {
this.remotePlatform = remotePlatform;
}
@Override
public String getRemoteBrowser() {
return remoteBrowser != null ? remoteBrowser : (parentOptions != null ? parentOptions.getRemoteBrowser() : null);
}
public void setRemoteBrowser(String remoteBrowser) {
this.remoteBrowser = remoteBrowser;
}
@Override
public String getRemoteVersion() {
return remoteVersion != null ? remoteVersion : (parentOptions != null ? parentOptions.getRemoteVersion() : null);
}
public void setRemoteVersion(String remoteVersion) {
this.remoteVersion = remoteVersion;
}
@Override
public boolean isHighlight() {
return highlight != null ? highlight : (parentOptions != null ? parentOptions.isHighlight() : false);
}
public void setHighlight(boolean highlight) {
this.highlight = highlight;
}
@Override
public boolean isInteractive() {
return interactive != null ? interactive : (parentOptions != null ? parentOptions.isInteractive() : false);
}
public void setInteractive(boolean interactive) {
this.interactive = interactive;
}
@Override
public String getScreenshotDir() {
return screenshotDir != null ? screenshotDir : (parentOptions != null ? parentOptions.getScreenshotDir() : null);
}
public void setScreenshotDir(String screenshotDir) {
this.screenshotDir = screenshotDir;
}
@Override
public String getScreenshotAll() {
return screenshotAll != null ? screenshotAll : (parentOptions != null ? parentOptions.getScreenshotAll() : null);
}
public void setScreenshotAll(String screenshotAll) {
this.screenshotAll = screenshotAll;
}
@Override
public String getScreenshotOnFail() {
return screenshotOnFail != null ? screenshotOnFail : (parentOptions != null ? parentOptions.getScreenshotOnFail() : null);
}
public void setScreenshotOnFail(String screenshotOnFail) {
this.screenshotOnFail = screenshotOnFail;
}
@Override
public boolean isIgnoreScreenshotCommand() {
return ignoreScreenshotCommand != null ? ignoreScreenshotCommand : (parentOptions != null ? parentOptions.isIgnoreScreenshotCommand() : false);
}
public void setIgnoreScreenshotCommand(boolean ignoreScreenshotCommand) {
this.ignoreScreenshotCommand = ignoreScreenshotCommand;
}
@Override
public String getBaseurl() {
return baseurl != null ? baseurl : (parentOptions != null ? parentOptions.getBaseurl() : null);
}
public void setBaseurl(String baseurl) {
this.baseurl = baseurl;
}
@Override
public String getFirefox() {
return firefox != null ? firefox : (parentOptions != null ? parentOptions.getFirefox() : null);
}
public void setFirefox(String firefox) {
this.firefox = firefox;
}
@Override
public String getGeckodriver() {
return geckodriver != null ? geckodriver : (parentOptions != null ? parentOptions.getGeckodriver() : null);
}
public void setGeckodriver(String geckodriver) {
this.geckodriver = geckodriver;
}
@Override
public String getChromedriver() {
return chromedriver != null ? chromedriver : (parentOptions != null ? parentOptions.getChromedriver() : null);
}
public void setChromedriver(String chromedriver) {
this.chromedriver = chromedriver;
}
@Override
public String getIedriver() {
return iedriver != null ? iedriver : (parentOptions != null ? parentOptions.getIedriver() : null);
}
public void setIedriver(String iedriver) {
this.iedriver = iedriver;
}
@Override
public String getPhantomjs() {
return phantomjs != null ? phantomjs : (parentOptions != null ? parentOptions.getPhantomjs() : null);
}
public void setPhantomjs(String phantomjs) {
this.phantomjs = phantomjs;
}
@Override
public String getXmlResult() {
return xmlResult != null ? xmlResult : (parentOptions != null ? parentOptions.getXmlResult() : null);
}
public void setXmlResult(String xmlResult) {
this.xmlResult = xmlResult;
}
@Override
public String getHtmlResult() {
return htmlResult != null ? htmlResult : (parentOptions != null ? parentOptions.getHtmlResult() : null);
}
public void setHtmlResult(String htmlResult) {
this.htmlResult = htmlResult;
}
@Override
public String getTimeout() {
return timeout != null ? timeout : (parentOptions != null ? parentOptions.getTimeout() : null);
}
public void setTimeout(String timeout) {
this.timeout = timeout;
}
@Override
public String getSetSpeed() {
return setSpeed != null ? setSpeed : (parentOptions != null ? parentOptions.getSetSpeed() : null);
}
public void setSetSpeed(String setSpeed) {
this.setSpeed = setSpeed;
}
@Override
public String getHeight() {
return height != null ? height : (parentOptions != null ? parentOptions.getHeight() : null);
}
public void setHeight(String height) {
this.height = height;
}
@Override
public String getWidth() {
return width != null ? width : (parentOptions != null ? parentOptions.getWidth() : null);
}
public void setWidth(String width) {
this.width = width;
}
@Override
public String[] getDefine() {
return define != null ? define : (parentOptions != null ? parentOptions.getDefine() : null);
}
public void addDefine(String defineItem) {
this.define = ArrayUtils.add(this.define, defineItem);
}
@Override
public String[] getRollup() {
return rollup != null ? rollup : (parentOptions != null ? parentOptions.getRollup() : null);
}
public void addRollup(String rollupItem) {
this.rollup = ArrayUtils.add(this.rollup, rollupItem);
}
@Override
public String getCookieFilter() {
return cookieFilter != null ? cookieFilter : (parentOptions != null ? parentOptions.getCookieFilter() : null);
}
public void setCookieFilter(String cookieFilter) {
this.cookieFilter = cookieFilter;
}
@Override
public String getCommandFactory() {
return commandFactory != null ? commandFactory : (parentOptions != null ? parentOptions.getCommandFactory() : null);
}
public void setCommandFactory(String commandFactory) {
this.commandFactory = commandFactory;
}
@Override
public boolean isNoExit() {
return noExit != null ? noExit : (parentOptions != null ? parentOptions.isNoExit() : false);
}
public void setNoExit(boolean noExit) {
this.noExit = noExit;
}
@Override
public boolean isStrictExitCode() {
return strictExitCode != null ? strictExitCode : (parentOptions != null ? parentOptions.isStrictExitCode() : false);
}
public void setStrictExitCode(boolean strictExitCode) {
this.strictExitCode = strictExitCode;
}
@Override
public String getMaxTime() {
return maxTime != null ? maxTime : (parentOptions != null ? parentOptions.getMaxTime() : null);
}
public void setMaxTime(String maxTime) {
this.maxTime = maxTime;
}
@Override
public boolean isHelp() {
return help != null ? help : (parentOptions != null ? parentOptions.isHelp() : false);
}
public void setHelp(boolean help) {
this.help = help;
}
@Override
public String[] getArgs() {
return args;
}
public void setArgs(String[] args) {
this.args = args;
}
/**
* Parse command line arguments.
*
* @param args command line arguments.
* @return parsed command line information.
* @throws IllegalArgumentException invalid options.
*/
public IConfig parseCommandLine(String... args) {
try {
parser.parseArgument(args);
if (config != null)
parentOptions = new DefaultConfig().loadFrom(config);
if (args == null)
args = LangUtils.EMPTY_STRING_ARRAY;
return this;
} catch (CmdLineException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
// Comment, KEY(1):VALUE(2), or NEXT_VALUE(3).
private static final Pattern RE = Pattern.compile("#.*|([\\w\\-]+)\\s*:\\s*(.*?)\\s*|\\s+(.*?)\\s*");
/**
* load configuration from file.
*
* @param file configuration file name.
* @return DefaultConfig object itself.
*/
public IConfig loadFrom(String file) {
try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
int cnt = 0;
String line;
String currentKey = null;
while ((line = r.readLine()) != null) {
cnt++;
if (line.isEmpty())
continue;
Matcher matcher = RE.matcher(line);
if (!matcher.matches())
throw new RuntimeException(file + ":" + cnt + ": Invalid format: " + line);
String key = matcher.group(1);
if (key != null) {
currentKey = key;
String value = matcher.group(2);
if (StringUtils.isNotEmpty(value))
setOrAddOptionValue(currentKey, value);
} else if (currentKey != null) {
String value = matcher.group(3);
if (StringUtils.isNotEmpty(value))
setOrAddOptionValue(currentKey, value);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
/**
* Set or add option value by name.
*
* @param name option name.
* @param value option value.
*/
public void setOrAddOptionValue(String name, String value) {
Class<?> type = optionMap.get(name).type;
if (type == String.class) {
set(name, value);
} else if (type == String[].class) {
add(name, value);
} else if (type == boolean.class || type == Boolean.class) {
set(name, BooleanUtils.toBoolean(value));
} else {
throw new UnsupportedOperationException(String.format("Can't set \"%s\" to option \"%s\" (%s)", value, name, type));
}
}
@Override
public <T> T get(String name) {
return optionMap.get(name).get(this);
}
/**
* Get option value by name.
* @param name option name.
* @param defaultValue default option value.
* @return option value.
*/
@Override
public <T> T get(String name, T defaultValue) {
T result = optionMap.get(name).get(this);
return result != null ? result : defaultValue;
}
/**
* Set option value by name.
* @param name option name.
* @param value option value.
*/
public void set(String name, Object value) {
optionMap.get(name).set(this, value);
}
/**
* Add value to option which has multiple parameters by name.
* @param name option name.
* @param value option value.
*/
public void add(String name, String value) {
optionMap.get(name).add(this, value);
}
private static final Pattern NOTE_RE = Pattern.compile("(\\*\\d+\\s+)(.*)");
/**
* Show help message.
*
* @param pw PrintWriter.
* @param title program title.
* @param version program version.
* @param cmdName command name.
* @param msgs messages.
*/
public void showHelp(PrintWriter pw, String title, String version, String cmdName, String... msgs) {
if (msgs.length > 0) {
for (String msg : msgs)
System.out.println(msg);
System.out.println();
}
int width = helpWidth - HELP_PADDING;
String seleniumVersion = SystemInformation.getInstance().getSeleniumVersion();
pw.println(WordUtils.wrap(title + " " + version + " (with Selenium " + seleniumVersion + ")", width));
pw.println();
for (String line : HEADER) {
if (line.isEmpty())
pw.println();
else
pw.println(WordUtils.wrap(line, width));
}
pw.println();
parser.printUsage(pw, null);
pw.println();
String padding = "";
for (String line : FOOTER) {
if (line.isEmpty()) {
padding = "";
pw.println();
} else {
Matcher m = NOTE_RE.matcher(line);
if (m.matches()) {
String mark = m.group(1);
padding = StringUtils.repeat(' ', mark.length());
String desc = WordUtils.wrap(m.group(2), width - mark.length(), "\n", false).replace("\n", SystemUtils.LINE_SEPARATOR + padding);
pw.print(mark);
pw.print(desc);
pw.println();
} else {
pw.print(padding);
pw.println(WordUtils.wrap(line, width - padding.length(), "\n", false).replace("\n", SystemUtils.LINE_SEPARATOR + padding));
}
}
}
pw.flush();
}
}