mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-20 11:38:28 -06:00
chromedriver working, verbs alpha
This commit is contained in:
parent
42a3a2f0af
commit
281542f42e
16
.run/NBCLI web foreground dryrun.run.xml
Normal file
16
.run/NBCLI web foreground dryrun.run.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="NBCLI web foreground dryrun" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="io.nosqlbench.engine.cli.NBCLI" />
|
||||
<module name="nb" />
|
||||
<option name="PROGRAM_PARAMETERS" value="run type=webdriver yaml=local/webtest cycles=1000 cyclerate=100 threads=1 dryrun=true -vv" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="io.nosqlbench.engine.cli.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -157,7 +157,7 @@ public class StdoutActivity extends SimpleActivity implements ActivityDefObserve
|
||||
|
||||
StringBindingsTemplate sbt = new StringBindingsTemplate(stmt.getStmt(), bt);
|
||||
StringBindings sb = sbt.resolve();
|
||||
sequencer.addOp(sb,Long.valueOf(stmt.getParams().getOrDefault("ratio","1")));
|
||||
sequencer.addOp(sb,Long.parseLong(stmt.getParams().getOrDefault("ratio","1")));
|
||||
}
|
||||
} else {
|
||||
logger.error("Unable to create a stdout statement if you have no active statements or bindings configured.");
|
||||
|
@ -1,12 +0,0 @@
|
||||
package io.nosqlbench.driver.web;
|
||||
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActivityType;
|
||||
|
||||
public class NBWebDriver implements ActivityType<WebDriverActivity> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package io.nosqlbench.driver.web;
|
||||
|
||||
import io.nosqlbench.engine.api.activityapi.core.BaseAsyncAction;
|
||||
import io.nosqlbench.engine.api.activityapi.core.ops.fluent.opfacets.TrackedOp;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* Holds the definition and tracking state for a web driver command.
|
||||
*/
|
||||
public class WebDriverAction extends BaseAsyncAction<WebDriverCmdState,WebDriverActivity> {
|
||||
|
||||
public WebDriverAction(WebDriverActivity activity, int slot) {
|
||||
super(activity,slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOpCycle(TrackedOp<WebDriverCmdState> opc) {
|
||||
// WebDriver driver = new ChromeDriver();
|
||||
// driver.get("http://docs.nosqlbench.io/");
|
||||
// opc.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongFunction<WebDriverCmdState> getOpInitFunction() {
|
||||
return (cycle) -> new WebDriverCmdState(this,cycle);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package io.nosqlbench.driver.web;
|
||||
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
|
||||
|
||||
public class WebDriverActivity extends SimpleActivity{
|
||||
|
||||
public WebDriverActivity(ActivityDef activityDef) {
|
||||
super(activityDef);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
import io.nosqlbench.engine.api.activityconfig.yaml.StmtDef;
|
||||
import io.nosqlbench.engine.api.activityimpl.motor.ParamsParser;
|
||||
import io.nosqlbench.nb.api.errors.BasicError;
|
||||
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
|
||||
import io.nosqlbench.virtdata.core.templates.ParsedTemplate;
|
||||
import io.nosqlbench.virtdata.core.templates.StringBindings;
|
||||
import io.nosqlbench.virtdata.core.templates.StringBindingsTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Use the {@link StmtDef} template form as a property template for parameterized
|
||||
* commands. This is a general purpose template which uses a map of named parameters.
|
||||
* The {@code command} property designates the verb component of the command.
|
||||
*/
|
||||
public class CommandTemplate {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(CommandTemplate.class);
|
||||
|
||||
private LinkedHashMap<String, StringBindings> cmdspec = new LinkedHashMap<>();
|
||||
|
||||
public CommandTemplate(StmtDef stmt) {
|
||||
|
||||
Map<String,String> cmdMap = ParamsParser.parse("command="+stmt.getStmt());
|
||||
Map<String, String> paramsMap = stmt.getParams();
|
||||
paramsMap.forEach((k,v) -> {
|
||||
if (cmdMap.containsKey(k)) {
|
||||
logger.warn("command property override: '" + k + "' superseded by param form with value '" + v + "'");
|
||||
}
|
||||
cmdMap.put(k,v);
|
||||
});
|
||||
|
||||
cmdMap.forEach((param,value) -> {
|
||||
ParsedTemplate paramTemplate = new ParsedTemplate(value, stmt.getBindings());
|
||||
BindingsTemplate paramBindings = new BindingsTemplate(paramTemplate.getBindPoints());
|
||||
StringBindings paramStringBindings = new StringBindingsTemplate(value, paramBindings).resolve();
|
||||
cmdspec.put(param,paramStringBindings);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public Map<String,String> getCommand(long cycle) {
|
||||
LinkedHashMap<String, String> cmd = new LinkedHashMap<>(cmdspec.size());
|
||||
cmdspec.forEach((k,v) -> {
|
||||
cmd.put(k,v.bind(cycle));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
import org.openqa.selenium.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class WebContext {
|
||||
private final static Logger logger = LoggerFactory.getLogger(WebContext.class);
|
||||
|
||||
private final WebDriver rootwindow;
|
||||
WebDriver focus;
|
||||
|
||||
LinkedList<WebElement> elements = new LinkedList<>();
|
||||
private LinkedList<Cookie> cookies = new LinkedList<>();
|
||||
private HashMap<String,Object> vars = new HashMap<>();
|
||||
private Alert alert;
|
||||
|
||||
public WebContext(WebDriver initial) {
|
||||
this.focus = initial;
|
||||
this.rootwindow = initial;
|
||||
}
|
||||
|
||||
public void pushElement(WebElement element) {
|
||||
elements.push(element);
|
||||
}
|
||||
|
||||
public WebElement peekElement() {
|
||||
return elements.peek();
|
||||
}
|
||||
|
||||
public LinkedList<Cookie> getCookies() {
|
||||
return cookies;
|
||||
}
|
||||
|
||||
public LinkedList<WebElement> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public void setElements(List<WebElement> elements) {
|
||||
this.elements.clear();
|
||||
this.elements.addAll(elements);
|
||||
}
|
||||
|
||||
public void pushCookie(Cookie cookie) {
|
||||
this.cookies.push(cookie);
|
||||
}
|
||||
|
||||
public void setCookies(Collection<Cookie> cookies) {
|
||||
this.cookies.clear();
|
||||
this.cookies.addAll(cookies);
|
||||
|
||||
}
|
||||
|
||||
public void setAlert(Alert alert) {
|
||||
this.alert = alert;
|
||||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return alert;
|
||||
}
|
||||
|
||||
public WebDriver driver() {
|
||||
return focus;
|
||||
}
|
||||
|
||||
public void setFocus(WebDriver driver) {
|
||||
this.focus =driver;
|
||||
}
|
||||
|
||||
public void clearAlert() {
|
||||
this.alert=null;
|
||||
}
|
||||
|
||||
public void setVar(String key, Object value) {
|
||||
this.vars.put(key,value);
|
||||
logger.debug("context vars: '" + key + "'='" + value.toString() + "'");
|
||||
}
|
||||
|
||||
public <T> T getVar(String name, Class<? extends T> type) {
|
||||
Object o = this.vars.get(name);
|
||||
if (o==null) { return null; }
|
||||
|
||||
if (type.isAssignableFrom(o.getClass())) {
|
||||
return type.cast(o);
|
||||
}
|
||||
throw new RuntimeException("Could not cast named var '" + name + "' to a " + type.getCanonicalName());
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.verbs.WebDriverVerbs;
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
|
||||
import io.nosqlbench.engine.api.activityapi.core.SyncAction;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* Holds the definition and tracking state for a web driver command.
|
||||
*/
|
||||
public class WebDriverAction implements SyncAction, ActivityDefObserver {
|
||||
|
||||
private final WebDriverActivity activity;
|
||||
private final int slot;
|
||||
// warn or count, or stop
|
||||
private String errors;
|
||||
private boolean dryrun;
|
||||
private WebContext context;
|
||||
|
||||
public WebDriverAction(WebDriverActivity activity, int slot) {
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
onActivityDefUpdate(activity.getActivityDef());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDefUpdate(ActivityDef activityDef) {
|
||||
this.errors = activityDef.getParams().getOptionalString("errors").orElse("stop");
|
||||
this.dryrun = activityDef.getParams().getOptionalBoolean("dryrun").orElse(false);
|
||||
this.context = activity.getWebContext(slot);
|
||||
}
|
||||
|
||||
// TODO: resolve inner templates early, perhaps optionally
|
||||
// As it is right now, all commands are resolved dynamically, which is still not going to be the limiting
|
||||
// factor.
|
||||
@Override
|
||||
public int runCycle(long value) {
|
||||
try {
|
||||
CommandTemplate commandTemplate = activity.getOpSequence().get(value);
|
||||
WebDriverVerbs.execute(value, commandTemplate, context, dryrun);
|
||||
return 0;
|
||||
} catch (Exception e) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
|
||||
import io.nosqlbench.engine.api.activityapi.planning.SequencePlanner;
|
||||
import io.nosqlbench.engine.api.activityapi.planning.SequencerType;
|
||||
import io.nosqlbench.engine.api.activityconfig.StatementsLoader;
|
||||
import io.nosqlbench.engine.api.activityconfig.yaml.StmtDef;
|
||||
import io.nosqlbench.engine.api.activityconfig.yaml.StmtsDocList;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
|
||||
import io.nosqlbench.engine.api.templating.StrInterpolator;
|
||||
import io.nosqlbench.nb.api.errors.BasicError;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.openqa.selenium.chrome.ChromeOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class WebDriverActivity extends SimpleActivity {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(WebDriverActivity.class);
|
||||
|
||||
private final StmtsDocList stmtsDocList;
|
||||
private OpSequence<CommandTemplate> opSequence;
|
||||
|
||||
private static ThreadLocal<WebContext> TL_WebContext = new ThreadLocal<>();
|
||||
private ConcurrentHashMap<Integer,WebContext> contexts = new ConcurrentHashMap<>();
|
||||
|
||||
public WebDriverActivity(ActivityDef activityDef) {
|
||||
super(activityDef);
|
||||
StrInterpolator interp = new StrInterpolator(activityDef);
|
||||
String yaml_loc = activityDef.getParams().getOptionalString("yaml", "workload").orElse("default");
|
||||
this.stmtsDocList = StatementsLoader.load(logger, yaml_loc, interp, "activities");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initActivity() {
|
||||
super.initActivity();
|
||||
this.opSequence = initOpSequence();
|
||||
onActivityDefUpdate(activityDef);
|
||||
}
|
||||
|
||||
private OpSequence<CommandTemplate> initOpSequence() {
|
||||
SequencerType sequencerType = getParams()
|
||||
.getOptionalString("seq")
|
||||
.map(SequencerType::valueOf)
|
||||
.orElse(SequencerType.bucket);
|
||||
SequencePlanner<CommandTemplate> planner = new SequencePlanner<>(sequencerType);
|
||||
|
||||
String tagfilter = activityDef.getParams().getOptionalString("tags").orElse("");
|
||||
List<StmtDef> stmts = stmtsDocList.getStmts(tagfilter);
|
||||
|
||||
if (stmts.size() == 0) {
|
||||
throw new BasicError("There were no active statements with tag filter '" + tagfilter + "'");
|
||||
}
|
||||
|
||||
for (StmtDef optemplate : stmts) {
|
||||
long ratio = Long.parseLong(optemplate.getParams().getOrDefault("ratio", "1"));
|
||||
CommandTemplate cmd = new CommandTemplate(optemplate);
|
||||
planner.addOp(cmd, ratio);
|
||||
}
|
||||
return planner.resolve();
|
||||
}
|
||||
|
||||
public OpSequence<CommandTemplate> getOpSequence() {
|
||||
return opSequence;
|
||||
}
|
||||
|
||||
|
||||
public synchronized WebContext getWebContext(int slot) {
|
||||
try {
|
||||
WebContext context = TL_WebContext.get();
|
||||
if (context == null) {
|
||||
logger.info("initializing chromedriver for thread " + slot);
|
||||
System.setProperty("webdriver.http.factory", "okhttp");
|
||||
ChromeOptions chromeOptions = new ChromeOptions();
|
||||
chromeOptions.setHeadless(activityDef.getParams().getOptionalBoolean("headless").orElse(false));
|
||||
WebDriver webdriver = new ChromeDriver(chromeOptions);
|
||||
context = new WebContext(webdriver);
|
||||
contexts.put(slot,context);
|
||||
TL_WebContext.set(context);
|
||||
} else {
|
||||
logger.info("using cached chromedriver for thread " + slot);
|
||||
}
|
||||
return context;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownActivity() {
|
||||
contexts.forEach((s, d) -> {
|
||||
logger.debug("closing driver for thread " + s);
|
||||
d.driver().quit();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package io.nosqlbench.driver.web;
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
import io.nosqlbench.engine.api.activityapi.core.Action;
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
|
@ -1,4 +1,4 @@
|
||||
package io.nosqlbench.driver.web;
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
public class WebDriverCmdState {
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Alert;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
public class AcceptAlert implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Alert alert = context.getAlert();
|
||||
if (alert==null) {
|
||||
alert = context.driver().switchTo().alert();
|
||||
}
|
||||
if (alert==null) {
|
||||
throw new InvalidParameterException("Alert is not present");
|
||||
}
|
||||
alert.accept();
|
||||
context.clearAlert();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
public class AddCookie implements WebDriverVerb {
|
||||
|
||||
private final Cookie cookie;
|
||||
|
||||
public AddCookie(Cookie cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().addCookie(this.cookie);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import io.nosqlbench.nb.api.errors.BasicError;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AssertElement implements WebDriverVerb {
|
||||
private final static Logger logger = LoggerFactory.getLogger(AssertElement.class);
|
||||
private final By by;
|
||||
|
||||
public AssertElement(By by) {
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebElement element = context.driver().findElement(by);
|
||||
context.pushElement(element);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public enum Bys {
|
||||
id(By::id),
|
||||
link(By::linkText),
|
||||
name(By::name),
|
||||
css(By::cssSelector),
|
||||
xpath(By::xpath),
|
||||
classname(By::className);
|
||||
|
||||
private Function<String, By> initializer;
|
||||
|
||||
Bys(Function<String,By> initializer) {
|
||||
this.initializer = initializer;
|
||||
}
|
||||
|
||||
public static By get(String by) {
|
||||
Bys bys =classname;
|
||||
String[] parts = by.split("=", 2);
|
||||
if (parts.length==2) {
|
||||
bys= Bys.valueOf(parts[0]);
|
||||
by = parts[1];
|
||||
}
|
||||
return bys.initializer.apply(by);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class ClearElement implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.peekElement().clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
public class ClearElementBy implements WebDriverVerb {
|
||||
private final By by;
|
||||
|
||||
public ClearElementBy(By by) {
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.peekElement().findElement(by).clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
public class ClickElementBy implements WebDriverVerb {
|
||||
|
||||
private final By by;
|
||||
|
||||
public ClickElementBy(By by){
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().findElement(by).click();
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CookieJar {
|
||||
public static Cookie cookie(Map<String,String> props) {
|
||||
String name = props.get("name");
|
||||
String value = props.getOrDefault("value","");
|
||||
Cookie.Builder builder = new Cookie.Builder(name, value);
|
||||
Optional.ofNullable(props.get("domain")).ifPresent(builder::domain);
|
||||
Optional.ofNullable(props.get("expiry")).map(Date::valueOf).ifPresent(builder::expiresOn);
|
||||
Optional.ofNullable(props.get("ishttponly")).map(Boolean::valueOf).ifPresent(builder::isHttpOnly);
|
||||
Optional.ofNullable(props.get("issecure")).map(Boolean::valueOf).ifPresent(builder::isSecure);
|
||||
Optional.ofNullable(props.get("path")).ifPresent(builder::path);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class DeleteAllCookies implements WebDriverVerb {
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().deleteAllCookies();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
public class DeleteCookie implements WebDriverVerb {
|
||||
|
||||
private final Cookie cookie;
|
||||
|
||||
public DeleteCookie(Cookie cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().deleteCookie(cookie);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class DeleteCookieNamed implements WebDriverVerb {
|
||||
|
||||
private final String cookieName;
|
||||
|
||||
public DeleteCookieNamed(String cookieName) {
|
||||
this.cookieName = cookieName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().deleteCookieNamed(cookieName);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Alert;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
public class DismissAlert implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Alert alert = context.getAlert();
|
||||
if (alert==null) {
|
||||
alert = context.driver().switchTo().alert();
|
||||
}
|
||||
if (alert==null) {
|
||||
throw new InvalidParameterException("Alert is not present");
|
||||
}
|
||||
alert.dismiss();
|
||||
context.clearAlert();
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.OutputType;
|
||||
|
||||
public class ElementScreenShot implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
String screenshotAs = context.peekElement().getScreenshotAs(OutputType.BASE64);
|
||||
// This needs more work
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class ElementSendKeys implements WebDriverVerb {
|
||||
private final CharSequence keys;
|
||||
|
||||
public ElementSendKeys(CharSequence keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.peekElement().sendKeys(keys);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class ElementSubmit implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.peekElement().submit();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FindElement implements WebDriverVerb {
|
||||
private final static Logger logger = LoggerFactory.getLogger(FindElement.class);
|
||||
|
||||
private final By by;
|
||||
|
||||
public FindElement(String by) {
|
||||
this.by = Bys.get(by);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
try {
|
||||
WebElement element = context.driver().findElement(by);
|
||||
context.pushElement(element);
|
||||
} catch (NoSuchElementException nsee) {
|
||||
context.pushElement(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
public class FindElementInElementBy implements WebDriverVerb {
|
||||
private final By by;
|
||||
|
||||
public FindElementInElementBy(By by) {
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebElement element = context.peekElement().findElement(by);
|
||||
context.pushElement(element);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FindElements implements WebDriverVerb {
|
||||
private final static Logger logger = LoggerFactory.getLogger(FindElements.class);
|
||||
|
||||
private final By by;
|
||||
|
||||
public FindElements(String by) {
|
||||
this.by = Bys.get(by);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
try {
|
||||
List<WebElement> elements = context.driver().findElements(by);
|
||||
context.setElements(elements);
|
||||
} catch (NoSuchElementException nsee) {
|
||||
context.pushElement(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FindElementsInElementBy implements WebDriverVerb {
|
||||
|
||||
private final By by;
|
||||
|
||||
public FindElementsInElementBy(By by) {
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
List<WebElement> elements = context.peekElement().findElements(by);
|
||||
context.setElements(elements);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class FullScreen implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().window().fullscreen();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Get implements WebDriverVerb {
|
||||
private final static Logger logger = LoggerFactory.getLogger(Get.class);
|
||||
|
||||
private final String target;
|
||||
|
||||
public Get(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().get(target);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
public class GetCookieNamed implements WebDriverVerb {
|
||||
private final String cookieName;
|
||||
|
||||
public GetCookieNamed(String cookieName) {
|
||||
this.cookieName = cookieName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Cookie cookie = context.driver().manage().getCookieNamed(cookieName);
|
||||
context.pushCookie(cookie);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class GetCookies implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Set<Cookie> cookies = context.driver().manage().getCookies();
|
||||
context.setCookies(cookies);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class GetElementAttribute implements WebDriverVerb {
|
||||
private final String name;
|
||||
|
||||
public GetElementAttribute(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
String attribute = context.peekElement().getAttribute(name);
|
||||
context.setVar("element.attribute",attribute);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
class GetElementCssValue implements WebDriverVerb {
|
||||
|
||||
private final String propname;
|
||||
public GetElementCssValue(String propname) {
|
||||
this.propname = propname;
|
||||
}
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
String cssValue = context.peekElement().getCssValue(propname);
|
||||
context.setVar("element.cssvalue",cssValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Point;
|
||||
|
||||
public class GetElementLocation implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Point location = context.peekElement().getLocation();
|
||||
context.setVar("element.location",location);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Rectangle;
|
||||
|
||||
public class GetElementRect implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Rectangle rect = context.peekElement().getRect();
|
||||
context.setVar("element.rect",rect);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Dimension;
|
||||
|
||||
public class GetElementSize implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Dimension size = context.peekElement().getSize();
|
||||
context.setVar("element.size",size);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class GetElementTagname implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
String tagName = context.peekElement().getTagName();
|
||||
context.setVar("element.tagname",tagName);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class GetElementText implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
String text = context.peekElement().getText();
|
||||
context.setVar("element.text",text);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class IsElementDisplayed implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
boolean displayed = context.peekElement().isDisplayed();
|
||||
context.setVar("element.displayed",displayed);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class IsElementEnabled implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
boolean enabled = context.peekElement().isEnabled();
|
||||
context.setVar("element.enabled",enabled);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class IsElementSelected implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
boolean selected = context.peekElement().isSelected();
|
||||
context.setVar("element.selected",selected);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class NavigateBack implements WebDriverVerb {
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().navigate().back();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class NavigateForward implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().navigate().forward();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class NavigateRefresh implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().navigate().refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public class NavigateTo implements WebDriverVerb {
|
||||
|
||||
private final URL url;
|
||||
|
||||
public NavigateTo(String to) {
|
||||
try {
|
||||
this.url = new URL(to);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().navigate().to(url);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Alert;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
public class SendKeysToAlert implements WebDriverVerb {
|
||||
private final String keys;
|
||||
|
||||
public SendKeysToAlert(String keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Alert alert = context.getAlert();
|
||||
if (alert==null) {
|
||||
alert = context.driver().switchTo().alert();
|
||||
}
|
||||
if (alert==null) {
|
||||
throw new InvalidParameterException("Alert is not present");
|
||||
}
|
||||
alert.sendKeys(keys);
|
||||
context.clearAlert();
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
public class SwitchToActiveElement implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebElement webElement = context.driver().switchTo().activeElement();
|
||||
context.pushElement(webElement);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Alert;
|
||||
|
||||
public class SwitchToAlert implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Alert alert = context.driver().switchTo().alert();
|
||||
context.setAlert(alert);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
public class SwitchToDefaultContent implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebDriver driver = context.driver().switchTo().defaultContent();
|
||||
context.setFocus(driver);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
public class SwitchToFrameByName implements WebDriverVerb {
|
||||
private String name;
|
||||
|
||||
public SwitchToFrameByName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebDriver frame = context.driver().switchTo().frame(this.name);
|
||||
context.setFocus(frame);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
public class SwitchToFrameElement implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebElement webElement = context.peekElement();
|
||||
if (webElement==null) {
|
||||
throw new InvalidParameterException("There was no previously found element to switch to.");
|
||||
}
|
||||
WebDriver frame = context.driver().switchTo().frame(webElement);
|
||||
context.setFocus(frame);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
public class SwitchToFrameElementBy implements WebDriverVerb {
|
||||
|
||||
private final By by;
|
||||
|
||||
public SwitchToFrameElementBy(String by) {
|
||||
this.by = Bys.get(by);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebElement element = context.driver().findElement(by);
|
||||
WebDriver frame = context.driver().switchTo().frame(element);
|
||||
context.setFocus(frame);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
public class SwitchToFrameIdx implements WebDriverVerb {
|
||||
private final int index;
|
||||
|
||||
public SwitchToFrameIdx(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebDriver frame = context.driver().switchTo().frame(index);
|
||||
context.setFocus(frame);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
public class SwitchToParentFrame implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebDriver webDriver = context.driver().switchTo().parentFrame();
|
||||
context.setFocus(webDriver);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
public class SwitchToWindow implements WebDriverVerb {
|
||||
private final String windowName;
|
||||
|
||||
public SwitchToWindow(String windowName) {
|
||||
this.windowName = windowName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
WebDriver window = context.driver().switchTo().window(this.windowName);
|
||||
context.setFocus(window);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* All WebDriverVerb implementations are executable commands.
|
||||
*/
|
||||
public interface WebDriverVerb {
|
||||
/**
|
||||
* Execute the command.
|
||||
*
|
||||
* @param context The context object allows verbs to do have state from one command to the next. It is a
|
||||
* {@link ThreadLocal} object which can be modified by any verb.
|
||||
*/
|
||||
void execute(WebContext context);
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.CommandTemplate;
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class WebDriverVerbs {
|
||||
private final static Logger logger = LoggerFactory.getLogger(WebDriverVerbs.class);
|
||||
public final static String COMMAND = "command";
|
||||
|
||||
public enum Verb {
|
||||
|
||||
get(Get.class, (m) -> new Get(m.get("target"))),
|
||||
find_element(FindElement.class, (m) -> new FindElement(m.get("by"))),
|
||||
find_elements(FindElements.class, (m) -> new FindElements(m.get("by"))),
|
||||
|
||||
// navigation
|
||||
back(NavigateBack.class, (m) -> new NavigateBack()),
|
||||
forward(NavigateForward.class, (m) -> new NavigateForward()),
|
||||
refresh(NavigateRefresh.class, (m) -> new NavigateRefresh()),
|
||||
to(NavigateTo.class, (m) -> new NavigateTo(m.get("to"))),
|
||||
|
||||
// options
|
||||
add_cookie(AddCookie.class, (m) -> new AddCookie(CookieJar.cookie(m))),
|
||||
delete_cookie(DeleteCookie.class, (m) -> new DeleteCookie(CookieJar.cookie(m))),
|
||||
delete_all_cookies(DeleteAllCookies.class, (m) -> new DeleteAllCookies()),
|
||||
delete_cookie_named(DeleteCookieNamed.class, (m) -> new DeleteCookieNamed(m.get("name"))),
|
||||
get_cookie_named(GetCookieNamed.class, (m) -> new GetCookieNamed(m.get("name"))),
|
||||
get_cookies(GetCookies.class, (m) -> new GetCookies()),
|
||||
|
||||
// TargetLocator (switchTo)
|
||||
switchto_active_element(SwitchToActiveElement.class, (m) -> new SwitchToActiveElement()),
|
||||
switchto_alert(SwitchToAlert.class, (m) -> new SwitchToAlert()),
|
||||
switchto_default_content(SwitchToDefaultContent.class, (m) -> new SwitchToDefaultContent()),
|
||||
switchto_frame_idx(SwitchToFrameIdx.class, (m) -> new SwitchToFrameIdx(Integer.parseInt(m.get("index")))),
|
||||
switchto_frame_elem_by(SwitchToFrameElementBy.class, (m) -> new SwitchToFrameElementBy(m.get("by"))),
|
||||
switchto_frame_elem(SwitchToFrameElement.class, (m) -> new SwitchToFrameElement()),
|
||||
switchto_frame_name(SwitchToFrameByName.class, (m) -> new SwitchToFrameByName(m.get("name"))),
|
||||
switchto_parent_frame(SwitchToParentFrame.class, (m) -> new SwitchToParentFrame()),
|
||||
switchto_window(SwitchToWindow.class, (m) -> new SwitchToWindow(m.get("name"))),
|
||||
|
||||
// Alert
|
||||
dismiss_alert(DismissAlert.class, (m) -> new DismissAlert()),
|
||||
accept_alert(AcceptAlert.class, (m) -> new AcceptAlert()),
|
||||
send_keys_to_alert(SendKeysToAlert.class, (m) -> new SendKeysToAlert(m.get("keys"))),
|
||||
|
||||
// Window
|
||||
window_full_screen(FullScreen.class, (m) -> new FullScreen()),
|
||||
window_get_size(WindowGetSize.class, (m) -> new WindowGetSize()),
|
||||
window_set_size(WindowSetSize.class, (m) -> new WindowSetSize(m.get("width"), m.get("height"))),
|
||||
window_maximize(WindowMaximize.class, (m) -> new WindowMaximize()),
|
||||
window_set_position(WindowSetPosition.class, (m) -> new WindowSetPosition(m.get("x"),m.get("y"))),
|
||||
window_get_position(WindowGetPosition.class, (m) -> new WindowGetPosition()),
|
||||
|
||||
// Elements
|
||||
click_element_by(ClickElementBy.class, (m) -> new ClickElementBy(Bys.get(m.get("by")))),
|
||||
clearElement(ClearElement.class, (m) -> new ClearElement()),
|
||||
clearElement_by(ClearElementBy.class, (m) -> new ClearElementBy(Bys.get(m.get("by")))),
|
||||
element_find_element_by(FindElementInElementBy.class, (m) -> new FindElementInElementBy(Bys.get(m.get("by")))),
|
||||
element_find_elements_by(FindElementsInElementBy.class, (m)->new FindElementsInElementBy(Bys.get(m.get("by")))),
|
||||
element_find_elements(FindElementsInElementBy.class, (m)->new FindElementsInElementBy(Bys.get(m.get("by")))),
|
||||
element_get_attribute(GetElementAttribute.class, (m) -> new GetElementAttribute(m.get("name"))),
|
||||
element_get_css_value(GetElementCssValue.class, (m) -> new GetElementCssValue(m.get("name"))),
|
||||
element_get_location(GetElementLocation.class, (m) -> new GetElementLocation()),
|
||||
element_get_rect(GetElementRect.class, (m) ->new GetElementRect()),
|
||||
element_get_size(GetElementSize.class, (m) -> new GetElementSize()),
|
||||
element_get_tagname(GetElementTagname.class, (m) -> new GetElementTagname()),
|
||||
element_get_text(GetElementText.class, (m) -> new GetElementText()),
|
||||
is_element_displayed(IsElementDisplayed.class, (m) -> new IsElementDisplayed()),
|
||||
is_element_enabled(IsElementEnabled.class, (m) -> new IsElementEnabled()),
|
||||
is_element_selected(IsElementSelected.class, (m) -> new IsElementSelected()),
|
||||
element_send_keys(ElementSendKeys.class, (m) -> new ElementSendKeys(m.get("keys"))),
|
||||
element_submit(ElementSubmit.class, (m) -> new ElementSubmit()),
|
||||
element_get_screenshot(ElementScreenShot.class, (m) -> new ElementScreenShot())
|
||||
|
||||
;
|
||||
|
||||
private final Class<? extends WebDriverVerb> verbClass;
|
||||
private final Function<Map<String, String>, WebDriverVerb> initializer;
|
||||
|
||||
Verb(Class<? extends WebDriverVerb> verbClass, Function<Map<String, String>, WebDriverVerb> initializer) {
|
||||
this.verbClass = verbClass;
|
||||
this.initializer = initializer;
|
||||
}
|
||||
|
||||
public WebDriverVerb resolve(Map<String, String> commandProps) {
|
||||
String cmd = commandProps.get(COMMAND);
|
||||
if (cmd == null) {
|
||||
throw new InvalidParameterException("command properties must always contain a 'command' entry'");
|
||||
}
|
||||
Verb verb = valueOf(cmd);
|
||||
return verb.initializer.apply(commandProps);
|
||||
}
|
||||
}
|
||||
|
||||
public static void execute(
|
||||
long cycle,
|
||||
CommandTemplate commandTemplate,
|
||||
WebContext context,
|
||||
boolean dryrun
|
||||
) {
|
||||
Map<String, String> cmdprops = commandTemplate.getCommand(cycle);
|
||||
logger.debug("cycle:" + cmdprops + " command:" + cmdprops);
|
||||
String command = cmdprops.get("command");
|
||||
Verb verb = Verb.valueOf(command);
|
||||
WebDriverVerb resolve = verb.resolve(cmdprops);
|
||||
if (dryrun) {
|
||||
logger.info("skipping cycle " + cycle + " because dryrun is set to true");
|
||||
return;
|
||||
} else {
|
||||
logger.info("running cycle " + cycle + " because dryrun is set to false");
|
||||
}
|
||||
resolve.execute(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Point;
|
||||
|
||||
public class WindowGetPosition implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Point position = context.driver().manage().window().getPosition();
|
||||
context.setVar("window.position", position);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Dimension;
|
||||
|
||||
public class WindowGetSize implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
Dimension size = context.driver().manage().window().getSize();
|
||||
context.setVar("window.size",size);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
|
||||
public class WindowMaximize implements WebDriverVerb {
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().window().maximize();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Point;
|
||||
|
||||
public class WindowSetPosition implements WebDriverVerb {
|
||||
private final int x;
|
||||
private final int y;
|
||||
|
||||
public WindowSetPosition(String x, String y) {
|
||||
this.x = Integer.parseInt(x);
|
||||
this.y = Integer.parseInt(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().window().setPosition(new Point(x, y));
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.nosqlbench.driver.webdriver.verbs;
|
||||
|
||||
import io.nosqlbench.driver.webdriver.WebContext;
|
||||
import org.openqa.selenium.Dimension;
|
||||
|
||||
public class WindowSetSize implements WebDriverVerb {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public WindowSetSize(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public WindowSetSize(String width, String height) {
|
||||
this.width = Integer.parseInt(width);
|
||||
this.height = Integer.parseInt(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WebContext context) {
|
||||
context.driver().manage().window().setSize(new Dimension(width, height));
|
||||
}
|
||||
}
|
3
driver-web/src/main/resources/webdriver.md
Normal file
3
driver-web/src/main/resources/webdriver.md
Normal file
@ -0,0 +1,3 @@
|
||||
# WebDriver Driver
|
||||
|
||||
welcome to the experimental webdriver prototype for nosqlbench.
|
@ -1,13 +1,11 @@
|
||||
package io.nosqlbench.driver.web;
|
||||
package io.nosqlbench.driver.webdriver;
|
||||
|
||||
import io.nosqlbench.nb.api.testutils.Perf;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
||||
import org.openqa.selenium.remote.RemoteWebElement;
|
||||
|
||||
|
||||
/**
|
@ -21,14 +21,65 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* <H2>Synopsis</H2>
|
||||
* <p>
|
||||
* This is a multi-purpose <em>special</em> parser for parameters in a command or
|
||||
* other map. It is called special because it does take a few liberties which are not
|
||||
* commonly found in regular languages, but which may be desirable for casual use.
|
||||
* This is done in a very specific way such that interpretation is unambiguous.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Basic Format</h2>
|
||||
* <p>
|
||||
* The input line consists of a sequence of names and values, called assignments.
|
||||
* These occur as {@code name=value}, and may be delimited by spaces or semicolons, like
|
||||
* {@code name1=value1 name2=value2} or {@code name1=value1;name2=value2}.
|
||||
* Values can even contain spaces, even though space is a possible delimiter.
|
||||
* Thus, {@code name1= value foo bar
|
||||
* name2=baz} works, with the values {@code value foo bar} and {@code baz}.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Names</h3>
|
||||
* The name of a parameter must appear in raw literal form, with no escaping. A name may not
|
||||
* contain spaces, semicolons or equals signs, and may not start with quotes of any kind.
|
||||
* Otherwise, there are no restrictions placed on the characters that may appear in
|
||||
* a name. Even escaped characters are allowed.
|
||||
*
|
||||
* <h3>Values</h3>
|
||||
* Values can appear as single quoted values, double quoted values, or literal values.
|
||||
* The way the value is parsed is determined by the leading character, respectively.
|
||||
*
|
||||
* <h4>Single Quoted</h4>
|
||||
* <p>If the leading character is a single quote, then all following characters up to the next single quote
|
||||
* are taken as the value, except for escape characters which work as expected. Double quotes,
|
||||
* spaces, equals or any other characters have no special meaning within a single quoted value.
|
||||
* </p>
|
||||
*
|
||||
* <H4>Double Quoted</H4>
|
||||
* <p>If the leading character is a double quote, then all following characters up to the next double quote
|
||||
* are taken as the value, except for escape characters which work as expected. Single quotes,
|
||||
* spaces, equals or other characters have no special meaning within double quotes.</p>
|
||||
*
|
||||
* <H4>Literal</H4>
|
||||
* <p>Otherwise, the value is taken as every single character, including spaces, single quotes,
|
||||
* and double quotes, up to the end of the command line or the next parameter assignment.
|
||||
* Any occurrence of semicolons which is not escaped will be treated as a delimiter. The next occurrence
|
||||
* of a name and equals pattern after a space will be taken as the next named parameter. Otherwise, all
|
||||
* spaces and partial word are included in the last value assigment found. Leading spaces on literal
|
||||
* values are skipped unless escaped.</p>
|
||||
*
|
||||
*/
|
||||
public class ParamsParser {
|
||||
|
||||
public static Map<String, String> parse(String input) {
|
||||
|
||||
ParseState s = ParseState.expectingName;
|
||||
|
||||
Map<String, String> parms = new LinkedHashMap<>();
|
||||
StringBuilder varname = new StringBuilder(128);
|
||||
StringBuilder value = new StringBuilder(128);
|
||||
StringBuilder varname = new StringBuilder(512);
|
||||
StringBuilder value = new StringBuilder(512);
|
||||
String lastVarname = null;
|
||||
boolean isEscaped = false;
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char c = input.charAt(i);
|
||||
@ -53,7 +104,10 @@ public class ParamsParser {
|
||||
|
||||
switch (s) {
|
||||
case expectingName:
|
||||
if (c != ' ' && c != ';') {
|
||||
if (c =='\'' || c=='"') {
|
||||
throw new RuntimeException("Unable to parse a name starting with character '" + c + "'. Names" +
|
||||
" must be literaal values.");
|
||||
} else if (c != ' ' && c != ';') {
|
||||
s = ParseState.readingName;
|
||||
varname.append(c);
|
||||
}
|
||||
@ -61,6 +115,14 @@ public class ParamsParser {
|
||||
case readingName:
|
||||
if (c == '\\') {
|
||||
isEscaped = true;
|
||||
} else if (c == ' ') {
|
||||
String partial = parms.get(lastVarname);
|
||||
if (partial==null) {
|
||||
throw new RuntimeException("space continuation while reading name or value, but no prior " +
|
||||
"for " + lastVarname + " exists");
|
||||
}
|
||||
parms.put(lastVarname,partial+" "+varname);
|
||||
varname.setLength(0);
|
||||
} else if (c != '=') {
|
||||
varname.append(c);
|
||||
} else {
|
||||
@ -68,7 +130,9 @@ public class ParamsParser {
|
||||
}
|
||||
break;
|
||||
case expectingVal:
|
||||
if (c == '\\') {
|
||||
if (c == ' ') {
|
||||
|
||||
} else if (c == '\\') {
|
||||
isEscaped = true;
|
||||
} else if (c == '\'') {
|
||||
s = ParseState.readingSquotedVal;
|
||||
@ -86,10 +150,11 @@ public class ParamsParser {
|
||||
case readingRawVal:
|
||||
if (c == '\\') {
|
||||
isEscaped = true;
|
||||
} else if (c != ';') {
|
||||
} else if (c != ';' && c != ' ') {
|
||||
value.append(c);
|
||||
} else {
|
||||
parms.put(varname.toString(), value.toString());
|
||||
lastVarname=varname.toString();
|
||||
varname.setLength(0);
|
||||
value.setLength(0);
|
||||
s = ParseState.expectingName;
|
||||
@ -139,6 +204,10 @@ public class ParamsParser {
|
||||
varname.setLength(0);
|
||||
s = ParseState.expectingName;
|
||||
break;
|
||||
case readingName:
|
||||
parms.put(lastVarname,parms.get(lastVarname)+' '+varname.toString());
|
||||
varname.setLength(0);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -118,5 +118,59 @@ public class ParamsParserTest {
|
||||
assertThat(p.get("b")).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpaceDelimiter() {
|
||||
Map<String, String> p = ParamsParser.parse("a=1 b=2");
|
||||
assertThat(p).hasSize(2);
|
||||
assertThat(p).containsKey("a");
|
||||
assertThat(p).containsKey("b");
|
||||
assertThat(p.get("a")).isEqualTo("1");
|
||||
assertThat(p.get("b")).isEqualTo("2");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpaceDelimiterGappedFirst() {
|
||||
Map<String, String> p = ParamsParser.parse("a=1 2 3 b=2");
|
||||
assertThat(p).hasSize(2);
|
||||
assertThat(p).containsKey("a");
|
||||
assertThat(p).containsKey("b");
|
||||
assertThat(p.get("a")).isEqualTo("1 2 3");
|
||||
assertThat(p.get("b")).isEqualTo("2");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpaceDelimiterGappedLast() {
|
||||
Map<String, String> p = ParamsParser.parse("a=1 b=2 3 4");
|
||||
assertThat(p).hasSize(2);
|
||||
assertThat(p).containsKey("a");
|
||||
assertThat(p).containsKey("b");
|
||||
assertThat(p.get("a")).isEqualTo("1");
|
||||
assertThat(p.get("b")).isEqualTo("2 3 4");
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testValidNameException() {
|
||||
Map<String, String> p = ParamsParser.parse("a=1\\\\;'\";b=2 3 4");
|
||||
assertThat(p).hasSize(2);
|
||||
assertThat(p).containsKey("a");
|
||||
assertThat(p).containsKey("b");
|
||||
assertThat(p.get("a")).isEqualTo("1\\;'\"");
|
||||
assertThat(p.get("b")).isEqualTo("2 3 4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkippingLiteralLeadingSpaces() {
|
||||
Map<String, String> p = ParamsParser.parse("a= fiver b=\\ sixer");
|
||||
assertThat(p).hasSize(2);
|
||||
assertThat(p).containsKey("a");
|
||||
assertThat(p).containsKey("b");
|
||||
assertThat(p.get("a")).isEqualTo("fiver");
|
||||
assertThat(p.get("b")).isEqualTo(" sixer");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -45,6 +45,12 @@
|
||||
<version>3.12.83-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.nosqlbench</groupId>
|
||||
<artifactId>driver-web</artifactId>
|
||||
<version>3.12.83-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.nosqlbench</groupId>
|
||||
<artifactId>activitytype-stdout</artifactId>
|
||||
|
@ -333,8 +333,9 @@ public class ParsedTemplate {
|
||||
*/
|
||||
public String getPositionalStatement(Function<String, String> tokenFormatter) {
|
||||
StringBuilder sb = new StringBuilder(spans[0]);
|
||||
|
||||
for (int i = 1; i < spans.length; i += 2) {
|
||||
sb.append(tokenFormatter.apply(spans[i]));
|
||||
sb.append(tokenFormatter!=null ? tokenFormatter.apply(spans[i]) : spans[i]);
|
||||
sb.append(spans[i + 1]);
|
||||
}
|
||||
return sb.toString();
|
||||
|
@ -1,9 +1,13 @@
|
||||
package io.nosqlbench.virtdata.core.templates;
|
||||
|
||||
//import io.nosqlbench.engine.api.activityconfig.ParsedStmt;
|
||||
//import io.nosqlbench.engine.api.activityconfig.yaml.StmtDef;
|
||||
import io.nosqlbench.virtdata.core.bindings.Bindings;
|
||||
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Uses a string template and a bindings template to create instances of {@link StringBindings}.
|
||||
@ -18,6 +22,30 @@ public class StringBindingsTemplate {
|
||||
this.bindingsTemplate = bindingsTemplate;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Build a default string bindings template using the standard representation
|
||||
// * for a string template in NoSQLBench, which is a literal string interspersed
|
||||
// * with named anchors in {@code {{curlybraces}}} form.
|
||||
// * @param stmtDef A stmtDef
|
||||
// */
|
||||
// public StringBindingsTemplate(StmtDef stmtDef) {
|
||||
// this(stmtDef, s->"{{"+s+"}}");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Build a string bindings template using a custom representation that maps
|
||||
// * the named anchors to a different form than the default {@code {{curlybraces}}} form.
|
||||
// * The mapping function provides the textual substitution which is used to composite
|
||||
// * the normative representation of the statement.
|
||||
// * @param stmtdef The {@link StmtDef} which provides the bindpoints
|
||||
// * @param tokenMapper A custom named anchor formatting function
|
||||
// */
|
||||
// public StringBindingsTemplate(StmtDef stmtdef, Function<String,String> tokenMapper) {
|
||||
// ParsedStmt parsedStmt = stmtdef.getParsed().orError();
|
||||
// this.stringTemplate = parsedStmt.getPositionalStatement(tokenMapper);
|
||||
// this.bindingsTemplate = new BindingsTemplate(parsedStmt.getBindPoints());
|
||||
// }
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link StringBindings}, preferably in the thread context that will use it.
|
||||
* @return a new StringBindings
|
||||
|
@ -332,7 +332,7 @@ from his cigarette.
|
||||
“Precisely. And the man who wrote the note is a German. Do you note the
|
||||
peculiar construction of the sentence—‘This account of you we have
|
||||
from all quarters received.’ A Frenchman or Russian could not have
|
||||
written that. It is the German who is so uncourteous to his verbs. It
|
||||
written that. It is the German who is so uncourteous to his verb. It
|
||||
only remains, therefore, to discover what is wanted by this German
|
||||
who writes upon Bohemian paper, and prefers wearing a mask to showing
|
||||
his face. And here he comes, if I am not mistaken, to resolve all our
|
||||
|
Loading…
Reference in New Issue
Block a user