
com.jdiai.JSLight Maven / Gradle / Ivy
package com.jdiai;
import com.epam.jdi.tools.Safe;
import com.epam.jdi.tools.Timer;
import com.epam.jdi.tools.map.MapArray;
import com.epam.jdi.tools.pairs.Pair;
import com.google.gson.JsonObject;
import com.jdiai.annotations.UI;
import com.jdiai.asserts.DisplayedTypes;
import com.jdiai.interfaces.HasCore;
import com.jdiai.interfaces.HasLocators;
import com.jdiai.jsbuilder.IJSBuilder;
import com.jdiai.jsdriver.JDINovaException;
import com.jdiai.jsdriver.JSDriver;
import com.jdiai.jsdriver.JSDriverUtils;
import com.jdiai.jsproducer.Json;
import com.jdiai.jswraper.JSSmart;
import com.jdiai.scripts.Whammy;
import com.jdiai.tools.*;
import com.jdiai.visual.Direction;
import com.jdiai.visual.ImageTypes;
import com.jdiai.visual.OfElement;
import com.jdiai.visual.StreamToImageVideo;
import org.apache.commons.lang3.NotImplementedException;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
import java.io.File;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import static com.epam.jdi.tools.EnumUtils.getEnumValue;
import static com.epam.jdi.tools.LinqUtils.*;
import static com.epam.jdi.tools.PrintUtils.print;
import static com.epam.jdi.tools.ReflectionUtils.*;
import static com.jdiai.JDI.conditions;
import static com.jdiai.jsbuilder.GetTypes.dataType;
import static com.jdiai.jsdriver.JSDriverUtils.*;
import static com.jdiai.jswraper.JSWrappersUtils.*;
import static com.jdiai.page.objects.PageFactoryUtils.getLocatorFromField;
import static com.jdiai.tools.FilterConditions.textEquals;
import static com.jdiai.tools.GetTextTypes.INNER_TEXT;
import static com.jdiai.tools.Keyboard.pasteText;
import static com.jdiai.tools.VisualSettings.*;
import static com.jdiai.visual.Direction.VECTOR_SIMILARITY;
import static com.jdiai.visual.ImageTypes.VIDEO_WEBM;
import static com.jdiai.visual.RelationsManager.*;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.String.format;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.openqa.selenium.OutputType.*;
public class JSLight implements JS {
public JSSmart js;
protected Supplier driver;
protected Safe actions;
protected String name = "";
protected Object parent = null;
protected JSImages imagesData;
public int renderTimeout = 5000;
protected String objectMap;
public JSLight() {
this(JDI::driver, new ArrayList<>());
}
public JSLight(Supplier driver, List locators) {
this.driver = driver;
this.js = new JSSmart(driver, locators);
// this.js.multiSearch();
this.actions = new Safe<>(() -> new Actions(driver()));
}
public JSLight(WebDriver driver, List locators) {
this(() -> driver, locators);
}
public JSLight(Supplier driver, By... locators) {
this(driver, newList(locators));
}
public JSLight(WebDriver driver, By... locators) {
this(() -> driver, locators);
}
public JSLight(JSLight parent, By locator) {
this(parent::driver, locator, parent);
}
public JSLight(WebDriver driver, By locator, Object parent) {
this(() -> driver, locator, parent);
}
public JSLight(Supplier driver, By locator, Object parent) {
this(driver, JSUtils.getLocators(locator, parent));
this.parent = parent;
if (parent != null && isInterface(parent.getClass(), HasCore.class)) {
this.js.updateDriver(((HasCore) parent).core().jsDriver().jsDriver());
}
}
public WebDriver driver() {
return this.driver.get();
}
public JavascriptExecutor js() {
return (JavascriptExecutor) driver();
}
public JS core() { return this; }
public void setCore(JS core) {
if (!isClass(core.getClass(), JSLight.class)) {
return;
}
JSLight jsLight = (JSLight) core;
this.js = jsLight.js;
this.driver = jsLight.driver;
this.actions = jsLight.actions;
this.name = jsLight.name;
this.parent = jsLight.parent;
this.imagesData = jsLight.imagesData;
this.renderTimeout = jsLight.renderTimeout;
this.objectMap = jsLight.objectMap;
}
// public void setCore(JS core) {
// List coreFields = getFieldsDeep(core);
// for (Field field : coreFields) {
// try {
// Field thisField = getClass().getField(field.getName());
// thisField.set(this, getValueField(field, core));
// } catch (Exception ignore) { }
// }
// }
@Override
public String getElement(String valueFunc) {
return js.getValue(valueFunc);
}
@Override
public List getList(String valueFunc) {
return js.getValues(valueFunc);
}
@Override
public String filterElements(String valueFunc) {
return js.firstValue(valueFunc);
}
@Override
public String getJSResult(String action) {
return js.getAttribute(action);
}
@Override
public void set(String action) {
doAction(action);
}
@Override
public void setOption(String option) {
if (option == null) {
return;
}
doAction("option.value = " + option + ";\nelement.dispatchEvent(new Event('change'));");
}
@Override
public void selectByName(String name) {
if (name == null) {
return;
}
doAction("dispatchEvent(new Event('change'));\n" +
"element.selectedIndex = [...element.options]" +
".findIndex(option => option.text === '" + name + "');\n" +
"element.dispatchEvent(new Event('change'));");
}
@Override
public boolean selectedByValueOption(String value) {
return core().getJSResult("selectedOptions[0].value").trim().equals(value);
}
@Override
public boolean selectedOption(String value) {
return core().getJSResult("selectedOptions[0].innerText").trim().equals(value);
}
@Override
public void doAction(String action) {
js.doAction(action);
}
public WebElement we() {
if (isEmpty(locators())) {
throw new JDINovaException("Failed to use we() because element has no locators");
}
SearchContext ctx = driver();
for (By locator : locators()) {
ctx = ctx.findElement(locator);
}
return (WebElement) ctx;
}
@Override
public void actionsWithElement(BiFunction action) {
action.apply(actions.get().moveToElement(this), this).build().perform();
}
@Override
public void actions(BiFunction action) {
action.apply(actions.get(), this).build().perform();
}
public String getName() {
return isNotBlank(name)
? name
: print(locators(), by -> JSDriverUtils.getByType(by) + ":" + JSDriverUtils.getByLocator(by), " > ");
}
@Override
public JSLight setName(String name) {
this.name = name;
return this;
}
public Object parent() {
return this.parent;
}
public JS setParent(Object parent) {
this.parent = parent;
return this;
}
public void click() {
doAction("click();");
}
@Override
public void clickCenter() {
doAction("let rect = element.getBoundingClientRect();" +
"let x = rect.x + rect.width / 2;" +
"let y = rect.y + rect.height / 2;" +
"document.elementFromPoint(x, y).click();");
}
@Override
public void click(int x, int y) {
js.jsExecute("document.elementFromPoint(" + x + ", " + y + ").click();");
}
@Override
public void select() { click(); }
@Override
public void select(String value) {
if (value == null || isEmpty(locators())) {
return;
}
By lastLocator = last(locators());
if (lastLocator.toString().contains("%s")) {
List locators = locators().size() == 1
? new ArrayList<>()
: locators().subList(0, locators().size() - 2);
locators.add(fillByTemplate(lastLocator, value));
new JSLight(driver, locators).click();
} else {
findFirst(textEquals(value)).click();
}
}
@Override
public void selectSubList(String value) {
if (value == null || isEmpty(locators())) {
return;
}
find(format(SELECT_FIND_TEXT_LOCATOR, value)).click();
}
public static String SELECT_FIND_TEXT_LOCATOR = ".//*[text()='%s']";
public String selectFindTextLocator = SELECT_FIND_TEXT_LOCATOR;
protected String selectFindTextLocator() {
return selectFindTextLocator;
}
@Override
public JS setFindTextLocator(String locator) {
selectFindTextLocator = locator;
return this;
}
@Override
public void select(String... values) {
if (isEmpty(values) || isEmpty(locators())) {
return;
}
By locator = last(locators());
IJSBuilder builder = getByLocator(locator).contains("%s")
? getTemplateScriptForSelect(locator, values)
: getScriptForSelect(values);
builder.executeQuery();
}
private IJSBuilder getTemplateScriptForSelect(By locator, String... values) {
IJSBuilder builder;
String ctx;
if (locators().size() == 1) {
builder = js.jsDriver().builder();
ctx = "document";
} else {
builder = new JSDriver(js.jsDriver().driver(), listCopyUntil(locators(), locators().size() - 1))
.buildOne();
ctx = "element";
}
builder.registerVariable("option");
builder.setElementName("option");
for (String value : values) {
By by = fillByTemplate(locator, value);
builder.oneToOne(ctx, by).doAction("option.click();\n");
}
return builder;
}
private IJSBuilder getScriptForSelect(String... values) {
IJSBuilder builder = js.jsDriver().buildOne();
builder.registerVariable("option");
builder.setElementName("option");
for (String value : values) {
By by = defineLocator(format(selectFindTextLocator(), value));
builder.oneToOne("element", by).doAction("option.click();\n");
}
return builder;
}
@Override
public > void select(TEnum name) {
select(getEnumValue(name));
}
@Override
public void check(boolean condition) {
doAction("checked=" + condition + ";");
}
@Override
public void check() {
check(true);
}
@Override
public void uncheck() {
check(false);
}
@Override
public void rightClick() {
actionsWithElement(Actions::contextClick);
}
@Override
public void doubleClick() {
actionsWithElement(Actions::doubleClick);
}
@Override
public void hover() {
actions(Actions::moveToElement);
}
@Override
public void dragAndDropTo(WebElement to) {
dragAndDropTo(to.getLocation().x, to.getLocation().y);
}
@Override
public void dragAndDropTo(int x, int y) {
actions((a,e) -> a.dragAndDropBy(e, x, y));
}
public void submit() {
doAction("submit()");
}
private String charToString(CharSequence... value) {
return value.length == 1 ? value[0].toString() : "";
}
public void sendKeys(CharSequence... value) {
if (value == null) {
return;
}
set("value+='" + charToString(value) + "';\nelement.dispatchEvent(new Event('input'));");
}
@Override
public void input(CharSequence... value) {
if (value == null) {
return;
}
set("value='" + charToString(value) + "';\nelement.dispatchEvent(new Event('input'));");
}
@Override
public void slide(String value) {
throw new NotImplementedException();
// TODO
//Actions a = new Actions(DRIVER.get());
//a.dragAndDropBy(DRIVER.get().findElement(By.xpath("[aria-labelledby='range-slider'][data-index='0']")),20, 0)
// .build().perform();
//js.jsDriver().builder().oneToOne("document", locators.get(0))
// .addJSCode("element.value='" + value + "';\n")
// .trigger("mousedown")
// .trigger("mousemove", "which: 1, pageX: 460");
//.trigger("mousedown")
// .trigger("mousemove", { which: 1, pageX: 460 })
}
public void clear() {
doAction("value = ''");
}
public String getTagName() {
return getJSResult("tagName").toLowerCase();
}
@Override
public String tag() {
return getTagName();
}
public String getAttribute(String attrName) {
return getJSResult("getAttribute('" + attrName + "')");
}
@Override
public String getProperty(String property) {
return getJSResult(property);
}
@Override
public Json getJson(String valueFunc) {
return js.getMap(valueFunc);
}
public String attr(String attrName) {
return getAttribute(attrName);
}
@Override
public List getAttributesAsList(String attr) {
return js.getAttributeList(attr);
}
public List attrList(String attr) {
return getAttributesAsList(attr);
}
public List getAttributesAsList(String... attr) {
return js.getMultiAttributes(attr);
}
public List attrList(String... attr) {
return getAttributesAsList(attr);
}
public List allClasses() {
String cl = attr("class");
return cl.length() > 0
? newList(cl.split(" "))
: new ArrayList<>();
}
public boolean hasClass(String className) {
return allClasses().contains(className);
}
public boolean hasAttribute(String attrName) {
return getJSResult("hasAttribute('" + attrName + "')").equals("true");
}
@Override
public Json allAttributes() {
return js.getMap("return '{'+[...element.attributes].map((attr)=> `'${attr.name}'='${attr.value}'`).join()+'}'");
}
public String printHtml() {
return MessageFormat.format("<{0} {1}>{2}{0}>", getTagName().toLowerCase(),
print(allAttributes(), el -> format("%s='%s'", el.key, el.value), " "),
getJSResult("innerHTML"));
}
public JS show() {
if (isDisplayed() && !isInView()) {
doAction("scrollIntoView({behavior:'auto',block:'center',inline:'center'})");
}
return this;
}
@Override
public void highlight(String color) {
show();
set("styles.border='3px dashed "+color+"'");
}
public void highlight() {
highlight("red");
}
@Override
public String cssStyle(String style) {
return js.getStyle(style);
}
@Override
public Json cssStyles(String... style) {
return js.getStyles(style);
}
@Override
public Json allCssStyles() {
return js.getAllStyles();
}
public boolean isSelected() {
return getProperty("checked").equals("true");
}
@Override
public boolean isDeselected() {
return !isSelected();
}
public boolean isEnabled() {
return hasAttribute("enabled");
}
@Override
public JS setTextType(GetTextTypes textType) {
this.textType = textType; return this;
}
public GetTextTypes textType = INNER_TEXT;
public String getText() {
return getText(textType);
}
@Override
public String getText(GetTextTypes textType) {
return getJSResult(textType.value);
}
public List findElements(By by) {
return we().findElements(by);
}
public WebElement findElement(By by) {
return we().findElement(by);
}
public boolean isDisplayed() {
return getElement(conditions.isDisplayed).equalsIgnoreCase("true");
}
public boolean isVisible() {
return getElement(DisplayedTypes.isVisible).equalsIgnoreCase("true");
}
public boolean isInView() {
if (isHidden()) {
return false;
}
Dimension visibleRect = getSize();
return visibleRect.height > 0 && visibleRect.width > 0;
}
public boolean isExist() {
return js.jsDriver().getSize() > 0;
}
public Point getLocation() {
ClientRect rect = getClientRect();
int x, y;
if (inVision(rect))
return new Point(-1, -1);
int left = max(rect.left, 0);
int top = max(rect.top, 0);
x = left + getWidth(rect) / 2;
y = top + getHeight(rect) / 2;
return new Point(x, y);
}
protected boolean inVision(ClientRect rect) {
return rect.x >= rect.windowWidth || rect.y >= rect.windowHeight || rect.bottom < 0 || rect.right < 0;
}
public Dimension getSize() {
ClientRect rect = getClientRect();
int width, height;
if (inVision(rect))
return new Dimension(0, 0);
width = getWidth(rect);
height = getHeight(rect);
return new Dimension(width, height);
}
private int getWidth(ClientRect rect) {
int left = max(rect.left, 0);
int right = min(rect.right, rect.windowWidth);
return right - left;
}
private int getHeight(ClientRect rect) {
int top = max(rect.top, 0);
int bottom = min(rect.bottom, rect.windowHeight);
return bottom - top;
}
public Rectangle getRect() {
ClientRect rect = getClientRect();
return inVision(rect)
? new Rectangle(0, 0, 0, 0)
: new Rectangle(rect.x, rect.y, getHeight(rect), getWidth(rect));
}
@Override
public ClientRect getClientRect() {
return new ClientRect(js.getJson("let rect = element.getBoundingClientRect();\n" +
"return { x: rect.x, y: rect.y, top: rect.top, bottom: rect.bottom, left: rect.left, right: rect.right, " +
"wWidth: window.innerWidth, wHeight: window.innerHeight };"));
}
public String getCssValue(String style) {
return js.getStyle(style);
}
public X getScreenshotAs(OutputType outputType) throws WebDriverException {
StreamToImageVideo screen = makeScreenshot(DEFAULT_IMAGE_TYPE);
if (outputType == BASE64) {
return (X) screen.asBase64();
}
if (outputType == BYTES) {
return (X) screen.asByteStream();
}
if (outputType == FILE) {
return (X) screen.asFile(IMAGE_TEMPLATE.apply("", this));
}
throw new JDINovaException("Failed to get screenshot - unknown type: " + outputType);
}
private String canvas2Image(ImageTypes imageType) {
return "toDataURL('" + imageType.value + "')";
}
private String element2Image(ImageTypes imageType) {
return "html2canvas(element).then((canvas) => canvas."+canvas2Image(imageType)+")";
}
public StreamToImageVideo makeScreenshot() {
return makeScreenshot(DEFAULT_IMAGE_TYPE);
}
@Override
public File makeScreenshot(String tag) {
show();
File imageFile = makeScreenshot().asFile(getScreenshotName(tag));
imagesData().images.update(tag, imageFile.getPath());
imagesData().imageFile = imageFile;
return imageFile;
}
protected String getScreenshotName(String tag) {
return IMAGE_TEMPLATE.apply(tag, this);
}
public StreamToImageVideo makeScreenshot(ImageTypes imageType) {
String stream = getElement("if (element.toDataURL) { return element."+canvas2Image(imageType)+"; }\n"
+ "try { return " + element2Image(imageType) + "; } catch {\n"
+ "return await import(`https://html2canvas.hertzen.com/dist/html2canvas.min.js`).then("
+ "() => " + element2Image(imageType) + ") }"
);
return new StreamToImageVideo(stream, imageType);
}
@Override
public void startRecording() {
startRecording(VIDEO_WEBM);
}
@Override
public void startRecording(ImageTypes imageType) {
String value = getElement("let blobs = [];\n" +
"const recorder = new MediaRecorder(element.captureStream(), { mimeType: '" + imageType.value + "' });\n" +
"recorder.ondataavailable = (e) => {\n" +
" if (e.data && e.data.size > 0) { blobs.push(e.data); }\n}\n" +
"recorder.onstop = () => {\n" +
" const blob = new Blob(blobs, { type: '" + imageType.value + "' });\n" +
" let reader = new FileReader();\n" +
" reader.readAsDataURL(blob);\n" +
" reader.onloadend = () => window.jdiVideoBase64 = reader.result;\n" +
"}\n" +
"recorder.start();\n" +
"window.jdiRecorder = recorder;\n" +
"return 'start recording'");
if (!value.equals("start recording")) {
throw new JDINovaException(value);
}
}
@Override
public StreamToImageVideo stopRecordingAndSave(ImageTypes imageType) {
js.jsExecute("window.jdiRecorder.stop();");
String stream = "";
Timer timer = new Timer(renderTimeout);
while (stream.length() < 10 && timer.isRunning()) {
stream = js.jsExecute("return window.jdiVideoBase64;");
}
return new StreamToImageVideo(stream, imageType);
}
@Override
public StreamToImageVideo stopRecordingAndSave() {
return stopRecordingAndSave(VIDEO_WEBM);
}
@Override
public StreamToImageVideo recordCanvasVideo(int sec) {
return recordCanvasVideo(VIDEO_WEBM, sec);
}
@Override
public StreamToImageVideo recordCanvasVideo(ImageTypes imageType, int sec) {
startRecording(imageType);
Timer.sleep((sec+1) * 1000L);
return stopRecordingAndSave(imageType);
}
// Experimental record video for any element
@Override
public StreamToImageVideo recordVideo(int sec) {
js.jsExecute("await import(`https://html2canvas.hertzen.com/dist/html2canvas.min.js`)");
getElement(Whammy.script);
Timer.sleep((sec+5) * 1000L);
js.jsExecute("jdi.recording = false; jdi.compile();");
String stream = "";
Timer timer = new Timer(renderTimeout);
while (stream.length() < 10 && timer.isRunning()) {
stream = js.jsExecute("return jdi.videoBase64");
}
return new StreamToImageVideo(stream, VIDEO_WEBM);
}
@Override
public JS setObjectMapping(String objectMap, Class> cl) {
this.objectMap = objectMap;
this.js.setupEntity(cl);
return this;
}
@Override
public JsonObject getJSObject(String json) {
return js.getJson(json);
}
@Override
public T getEntity(Class cl) {
return getEntity(GET_OBJECT_MAP.apply(cl), cl);
}
@Override
public T getEntity() {
return js.getEntity(objectMap);
}
@Override
public void setEntity() {
js.setEntity(objectMap);
}
@Override
public T getEntity(String objectMap, Class> cl) {
js.setupEntity(cl);
return js.getEntity(objectMap);
}
@Override
public void setEntity(String objectMap) {
js.setEntity(objectMap);
}
@Override
public JS find(String by) {
return find(NAME_TO_LOCATOR.apply(by));
}
@Override
public JS find(By by) {
return new JSLight(this, by);
}
@Override
public JS children() {
return find("*");
}
@Override
public JS ancestor() {
return find("/..");
}
@Override
public List values(GetTextTypes getTextType) {
return js.getAttributeList(getTextType.value);
}
@Override
public List values() {
return values(textType);
}
@Override
public int size() {
return js.getSize();
}
@Override
public List getObjectList(String json) {
return js.getJsonList(json);
}
@Override
public List getEntityList() {
return js.getEntityList(objectMap);
}
@Override
public void setEntityList() {
js.setEntity(objectMap);
}
public static Function GET_COMPLEX_VALUE = field -> {
if (!field.isAnnotationPresent(FindBy.class) && !field.isAnnotationPresent(UI.class)) {
return null;
}
By locator = getLocatorFromField(field);
if (locator != null) {
String element = MessageFormat.format(dataType(locator).get, "element", getByLocator(locator));
return format("'%s': %s", field.getName(), getValueType(field, element));
}
return null;
};
public static BiFunction SET_COMPLEX_VALUE = (field, value)-> {
if (!field.isAnnotationPresent(FindBy.class) && !field.isAnnotationPresent(UI.class))
return null;
By locator = getLocatorFromField(field);
if (locator == null) {
return null;
}
String element = MessageFormat.format(dataType(locator).get, "element", getByLocator(locator));
return setValueType(field, element, value);
};
public static Function, String> GET_OBJECT_MAP = cl -> {
Field[] allFields = cl.getDeclaredFields();
List mapList = new ArrayList<>();
for (Field field : allFields) {
String value = GET_COMPLEX_VALUE.apply(field);
if (value != null) {
mapList.add(value);
}
}
return "{ " + print(mapList, ", ") + " }";
};
@Override
public List getEntityList(Class cl) {
return getEntityList(GET_OBJECT_MAP.apply(cl), cl);
}
@Override
public void fill(Object obj) {
setEntity(obj);
}
@Override
public void submit(Object obj, String locator) {
setEntity(obj);
find(locator).click();
}
@Override
public void submit(Object obj) {
submit(obj, SUBMIT_LOCATOR);
}
@Override
public void loginAs(Object obj, String locator) {
submit(obj, locator);
}
@Override
public void loginAs(Object obj) {
submit(obj);
}
public static String SUBMIT_LOCATOR = "[type=submit]";
@Override
public JS setEntity(Object obj) {
Field[] allFields = obj.getClass().getDeclaredFields();
List mapList = new ArrayList<>();
for (Field field : allFields) {
Object fieldValue = getValueField(field, obj);
if (fieldValue == null) {
continue;
}
String value = SET_COMPLEX_VALUE.apply(field, fieldValue);
if (value != null) {
mapList.add(value);
}
}
setEntity(print(mapList, ";\n") + ";\nreturn ''");
return this;
}
@Override
public List getEntityList(String objectMap, Class> cl) {
js.setupEntity(cl);
return js.getEntityList(objectMap);
}
@Override
public void setEntityList(String objectMap) {
js.setEntity(objectMap);
}
@Override
public JS findFirst(String by, Function condition) {
return findFirst(NAME_TO_LOCATOR.apply(by), condition.apply(this));
}
@Override
public JS findFirst(By by, Function condition) {
return findFirst(by, condition.apply(this));
}
@Override
public JS findFirst(String by, String condition) {
return findFirst(NAME_TO_LOCATOR.apply(by), condition);
}
@Override
public JS get(int index) {
return listToOne("element = elements[" + index + "];\n")
.setName(format("%s[%s]",getName(), index));
}
@Override
public JS get(String by, int index) {
return get(NAME_TO_LOCATOR.apply(by), index);
}
@Override
public JS get(By by, int index) {
String script = "element = elements.filter(e => "+
MessageFormat.format(dataType(by).get, "e", selector(by, js.jsDriver().builder()))+
")[" + index + "];\n";
return listToOne(script)
.setName(format("%s[%s]",getName(), index)).core();
}
@Override
public JSLight get(Function filter) {
return findFirst(filter);
}
@Override
public JSLight get(String value) {
return get(textEquals(value))
.setName(format("%s[%s]",getName(), value));
}
@Override
public JSLight findFirst(Function condition) {
return findFirst(condition.apply(this));
}
@Override
public JSLight findFirst(String condition) {
return listToOne("element = elements.find(e => e && " + handleCondition(condition, "e") + ");\n");
}
private String handleCondition(String condition, String elementName) {
return condition.contains("#element#")
? condition.replace("#element#", elementName)
: elementName + "." + condition;
}
@Override
public JS findFirst(By by, String condition) {
String script = "element = elements.find(e => { const fel = " +
MessageFormat.format(dataType(by).get, "e", selector(by, js.jsDriver().builder())) + "; " +
"return fel && " + handleCondition(condition, "fel") + "; });\n";
return listToOne(script);
}
@Override
public long indexOf(Function condition) {
return js.jsDriver().indexOf(condition.apply(this));
}
private JSLight listToOne(String script) {
JSLight result = new JSLight(driver);
result.js.jsDriver().setScriptInElementContext(js.jsDriver(), script);
js.jsDriver().builder().cleanup();
return result;
}
// TODO
// public WebList finds(@MarkupLocator String by) {
// return $$(by, this);
// }
// public WebList finds(@MarkupLocator By by) {
// return $$(by, this);
// }
public boolean isClickable() {
Dimension dimension = getSize();
if (dimension.getWidth() == 0) return false;
return isClickable(dimension.getWidth() / 2, dimension.getHeight() / 2 - 1);
}
@Override
public void uploadFile(String filePath) {
we().click();
String pathToPaste = new File(filePath).getAbsolutePath();
pasteText(pathToPaste);
}
@Override
public void press(Keys key) {
Keyboard.press(key);
}
@Override
public void keyboardCommands(String... commands) {
Keyboard.commands(commands);
}
@Override
public boolean isClickable(int xOffset, int yOffset) {
return getElement("rect = element.getBoundingClientRect();\n" +
"cx = rect.left + " + xOffset + ";\n" +
"cy = rect.top + " + yOffset + ";\n" +
"e = document.elementFromPoint(cx, cy);\n" +
"for (; e; e = e.parentElement) {\n" +
" if (e === element)\n" +
" return true;\n" +
"}\n" +
"return false;").equals("true");
}
@Override
public String fontColor() {
return js.color();
}
@Override
public String bgColor() {
return js.bgColor();
}
@Override
public String pseudo(String name, String value) {
return js.pseudo(name, value);
}
@Override
public boolean focused() {
return getElement("element === document.activeElement").equalsIgnoreCase("true");
}
@Override
public List locators() {
return js.jsDriver().locators();
}
@Override
public JSImages imagesData() {
if (imagesData == null) {
imagesData = new JSImages();
}
return imagesData;
}
@Override
public File getImageFile() {
return getImageFile("");
}
@Override
public File getImageFile(String tag) {
return imagesData().images.has(tag) ? new File(imagesData().images.get(tag)) : null;
}
@Override
public void visualValidation() {
visualValidation("");
}
@Override
public void visualValidation(String tag) {
VISUAL_VALIDATION.accept(tag, this);
}
@Override
public void visualCompareWith(JS element) {
COMPARE_IMAGES.apply(imagesData().imageFile, element.imagesData().imageFile);
}
public Direction getDirectionTo(WebElement element) {
Rectangle elementCoordinates = getRect();
Rectangle destinationCoordinates = element.getRect();
Direction direction = new Direction(getCenter(elementCoordinates), getCenter(destinationCoordinates));
if (isInterface(element.getClass(), HasCore.class)) {
JS core = ((HasCore)element).core();
if (relations == null) {
relations = new MapArray<>(core.getFullName(), direction);
} else {
relations.update(core.getFullName(), direction);
}
}
return direction;
}
@Override
public boolean relativePosition(JS element, Direction expected) {
return COMPARE_POSITIONS.apply(getDirectionTo(element), expected);
}
@Override
public OfElement isOn(Function expected) {
return new OfElement(expected, this);
}
@Override
public boolean relativePosition(JS element, Function expected) {
return expected.apply(getDirectionTo(element));
}
public MapArray relations;
@Override
public void clearRelations() {
relations = null;
}
@Override
public MapArray getRelativePositions(JS... elements) {
relations = new MapArray<>();
for (JS element : elements) {
relations.update(element.getName(), getDirectionTo(element));
}
storeRelations(this, relations);
return relations;
}
private boolean similar(Pair relation, Direction expectedRelation) {
return VECTOR_SIMILARITY.apply(relation.value, expectedRelation);
}
@Override
public List validateRelations() {
MapArray storedRelations = readRelations(this);
if (isEmpty(storedRelations)) {
return newList("No relations found in: " + RELATIONS_STORAGE);
}
List failures = new ArrayList<>();
if (isEmpty(relations)) {
return newList("No element relations found: use getRelativePosition(...) first and save element relations");
}
MapArray newRelations = getRelations(storedRelations, failures);
if (isNotEmpty(newRelations)) {
storeRelations(this, newRelations);
}
return failures;
}
private MapArray getRelations(MapArray storedRelations, List failures) {
MapArray newRelations = new MapArray<>();
for (Pair relation : relations) {
if (storedRelations.has(relation.key)) {
checkRelations(storedRelations.get(relation.key), relation, failures);
} else {
newRelations.add(relation);
}
}
return newRelations;
}
private void checkRelations(Direction expectedRelation, Pair relation, List failures) {
if (similar(relation, expectedRelation)) {
return;
}
failures.add(format("Elements '%s' and '%s' are misplaced: angle: %s => %s; length: %s => %s",
getFullName(), relation.key, relation.value.angle(), expectedRelation.angle(),
relation.value.length(), expectedRelation.length()));
}
@Override
public Point getCenter() {
return getCenter(getRect());
}
protected Point getCenter(Rectangle rect) {
int x = rect.x + rect.width / 2;
int y = rect.y + rect.height / 2;
return new Point(x, y);
}
@Override
public JSSmart jsDriver() {
return js;
}
@Override
public String textType() {
return textType.value;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy