parent
6859cb6493
commit
34c52c0023
@ -0,0 +1,283 @@
|
|||||||
|
package com.olexyn.tabdriver;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.olexyn.min.log.LogU;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.Capabilities;
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.Keys;
|
||||||
|
import org.openqa.selenium.NoSuchFrameException;
|
||||||
|
import org.openqa.selenium.SearchContext;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriverService;
|
||||||
|
|
||||||
|
public class TabDriver extends ChromeDriver implements JavascriptExecutor {
|
||||||
|
|
||||||
|
private final Map<String, Tab> TABS = new HashMap<>();
|
||||||
|
|
||||||
|
public TabDriver(ChromeDriverService service, Capabilities capabilities) {
|
||||||
|
super(service, capabilities);
|
||||||
|
manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void registerCurrentTab(String purpose) {
|
||||||
|
Tab tab = new Tab(getWindowHandle());
|
||||||
|
tab.setName(getTitle());
|
||||||
|
tab.setUrl(getCurrentUrl());
|
||||||
|
tab.setPurpose(purpose);
|
||||||
|
TABS.put(tab.getHandle(), tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Tab getCurrentTab() {
|
||||||
|
return TABS.get(getWindowHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<Tab> getTabByPurpose(String purpose) {
|
||||||
|
return TABS.values().stream()
|
||||||
|
.filter(x -> x.getPurpose() == purpose)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String registerBlankTab(String purpose) {
|
||||||
|
Set<String> openTabHandles = getWindowHandles();
|
||||||
|
for (String openTabHandle : openTabHandles) {
|
||||||
|
if (!TABS.containsKey(openTabHandle)) {
|
||||||
|
Tab blankTab = new Tab(openTabHandle);
|
||||||
|
blankTab.setName("about:blank");
|
||||||
|
blankTab.setUrl("about:blank");
|
||||||
|
blankTab.setPurpose(purpose);
|
||||||
|
TABS.put(openTabHandle, blankTab);
|
||||||
|
return openTabHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogU.warnPlain("Not unregistered tab found.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String registerExistingTab(String purpose) {
|
||||||
|
String handle = getWindowHandle();
|
||||||
|
Tab tab = new Tab(handle);
|
||||||
|
tab.setName(getTitle());
|
||||||
|
tab.setUrl(getCurrentUrl());
|
||||||
|
tab.setPurpose(purpose);
|
||||||
|
TABS.put(handle, tab);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void switchToTab(String handle) {
|
||||||
|
for (Entry<String, Tab> entry : TABS.entrySet()) {
|
||||||
|
String tabHandle = entry.getKey();
|
||||||
|
if (tabHandle.equals(handle)) {
|
||||||
|
switchTo().window(tabHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void switchToTab(Tab tab) {
|
||||||
|
switchTo().window(tab.getHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a new tab, and "moves" the WebDriver to the new tab.
|
||||||
|
* If the current tab is empty, it is registered - this happens usually only with the initial tab of the session.
|
||||||
|
*/
|
||||||
|
public synchronized void newTab(String purpose) {
|
||||||
|
String currentUrl = getCurrentUrl();
|
||||||
|
if (currentUrl.isEmpty()
|
||||||
|
|| currentUrl.equals("data:,")
|
||||||
|
|| currentUrl.equals("about:blank")) {
|
||||||
|
registerExistingTab(purpose);
|
||||||
|
} else {
|
||||||
|
executeScript("window.open(arguments[0])");
|
||||||
|
switchToTab(registerBlankTab(purpose));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void switchToTabByPurpose(String purpose) {
|
||||||
|
List<Tab> existingTabs = getTabByPurpose(purpose);
|
||||||
|
if (!existingTabs.isEmpty()) {
|
||||||
|
switchToTab(existingTabs.get(0));
|
||||||
|
} else {
|
||||||
|
Tab currentTab = getCurrentTab();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void refresh() {
|
||||||
|
navigate().refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void get(String url) {
|
||||||
|
super.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized WebElement findElement(By by) {
|
||||||
|
return super.findElement(by);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void executeScript(String script) {
|
||||||
|
((JavascriptExecutor) this).executeScript(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void sendDeleteKeys(WebElement element, int n) {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
element.sendKeys(Keys.BACK_SPACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized WebElement filterElementListBy(List<WebElement> list, CRITERIA criteria, String text) {
|
||||||
|
for (WebElement element : list) {
|
||||||
|
String toEvaluate = switch (criteria) {
|
||||||
|
case CLASS -> element.getClass().getName();
|
||||||
|
case TEXT -> element.getText();
|
||||||
|
case TAG -> element.getTagName();
|
||||||
|
case HREF -> element.getAttribute("href");
|
||||||
|
case ID -> element.getAttribute("id");
|
||||||
|
case TITLE -> element.getAttribute("title");
|
||||||
|
case NONE -> text;
|
||||||
|
};
|
||||||
|
if (toEvaluate != null && toEvaluate.contains(text)) { return element; }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String FRAME_ID_DEFAULT_CONTENT = "defaultContent";
|
||||||
|
private static final String FRAME_ID_NONE_FOUND = "noneFound";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all frames accessible to WebDriver.
|
||||||
|
*/
|
||||||
|
public synchronized Map<String, String> collectAllFrames() throws NoSuchFrameException {
|
||||||
|
Map<String, String> mapOfCollectedSources = new HashMap<>();
|
||||||
|
switchTo().defaultContent();
|
||||||
|
mapOfCollectedSources.put(FRAME_ID_DEFAULT_CONTENT, getPageSource());
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
try {
|
||||||
|
switchTo().defaultContent();
|
||||||
|
switchTo().frame(i);
|
||||||
|
mapOfCollectedSources.put(String.valueOf(i), getPageSource());
|
||||||
|
} catch (NoSuchFrameException e) {
|
||||||
|
return mapOfCollectedSources;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapOfCollectedSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String findFrameContainingCharSeq(Map<String, String> mapOfCollectedSources, String string) {
|
||||||
|
for (Entry<String, String> entry : mapOfCollectedSources.entrySet()) {
|
||||||
|
if (entry.getValue().contains(string)) {
|
||||||
|
return entry.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FRAME_ID_NONE_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public boolean isJavascriptEnabled() {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public enum CRITERIA {
|
||||||
|
CLASS,
|
||||||
|
TEXT,
|
||||||
|
TAG,
|
||||||
|
HREF,
|
||||||
|
NONE,
|
||||||
|
ID,
|
||||||
|
TITLE
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void switchToFrameContainingCharSeq(String charSeq) {
|
||||||
|
switchTo().defaultContent();
|
||||||
|
sleep(500);
|
||||||
|
final String frameId = findFrameContainingCharSeq(collectAllFrames(), charSeq);
|
||||||
|
sleep(400);
|
||||||
|
switch (frameId) {
|
||||||
|
case FRAME_ID_DEFAULT_CONTENT:
|
||||||
|
switchTo().defaultContent();
|
||||||
|
break;
|
||||||
|
case FRAME_ID_NONE_FOUND:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
switchTo().frame(Integer.parseInt(frameId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sleep(long milli) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(milli);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogU.warnPlain("SLEEP was INTERRUPED.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first occurrence of specified class that has specified label.
|
||||||
|
*/
|
||||||
|
public synchronized WebElement getWhere(String className, CRITERIA criteria, String text) {
|
||||||
|
switchToFrameContainingCharSeq(text);
|
||||||
|
List<WebElement> elements = findElements(By.className(className));
|
||||||
|
return filterElementListBy(elements, criteria, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized WebElement getWhere(String className) {
|
||||||
|
switchToFrameContainingCharSeq(className);
|
||||||
|
List<WebElement> elements = findElements(By.className(className));
|
||||||
|
return filterElementListBy(elements, CRITERIA.NONE, Constants.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void followContainedLink(WebElement element) {
|
||||||
|
String link = element.getAttribute("href");
|
||||||
|
if (link != null) { navigate().to(link); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setRadio(By by, boolean checked) {
|
||||||
|
((JavascriptExecutor) this).executeScript("arguments[0].checked = " + checked + ";", findElement(by));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setComboByDataValue(By comboBy, String dataValue) {
|
||||||
|
WebElement combo = findElement(comboBy);
|
||||||
|
combo.click();
|
||||||
|
combo.findElement(By.cssSelector("li[data-value='" + dataValue + "']")).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any-Match.
|
||||||
|
*/
|
||||||
|
public synchronized WebElement getByFieldValue(String type, String field, String value) {
|
||||||
|
return findElement(By.cssSelector(type + "[" + field + "*='" + value + "']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void clickByFieldValue(String type, String field, String value) {
|
||||||
|
var we = getByFieldValue(type, field, value);
|
||||||
|
click(we);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param field can contain wildcards like "class*" or "class^"
|
||||||
|
* @param value can contain partial matches like "last-"
|
||||||
|
*/
|
||||||
|
public synchronized WebElement getByFieldValue(SearchContext searchContext, String type, String field, String value) {
|
||||||
|
|
||||||
|
return searchContext.findElement(By.cssSelector(type + "[" + field + "='" + value + "']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized WebElement getByText(String text) {
|
||||||
|
return findElement(By.xpath("//*[contains(text(),'" + text + "')]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void click(WebElement we) {
|
||||||
|
executeScript("arguments[0].click();", we);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.olexyn.tabdriver;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import com.olexyn.PropConf;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriverService;
|
||||||
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
|
import org.openqa.selenium.remote.CapabilityType;
|
||||||
|
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class TabDriverBuilder {
|
||||||
|
|
||||||
|
private static Path CONFIG_PATH;
|
||||||
|
|
||||||
|
public static TabDriver build(Path path) {
|
||||||
|
CONFIG_PATH = path;
|
||||||
|
if (CONFIG_PATH == null) {
|
||||||
|
throw new RuntimeException("CONFIG_PATH must be set to an absolute path.");
|
||||||
|
}
|
||||||
|
return new TabDriver(configureService(), configureCapabilities());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static ChromeDriverService configureService() {
|
||||||
|
PropConf.loadProperties(CONFIG_PATH.toString());
|
||||||
|
var path = Path.of(PropConf.get("chrome.driver.path"));
|
||||||
|
return new ChromeDriverService.Builder()
|
||||||
|
.usingDriverExecutable(path.toFile())
|
||||||
|
.usingAnyFreePort()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DesiredCapabilities configureCapabilities() {
|
||||||
|
var cap = DesiredCapabilities.chrome();
|
||||||
|
cap.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
|
||||||
|
|
||||||
|
ChromeOptions options = new ChromeOptions();
|
||||||
|
options.addArguments("--start-maximized");
|
||||||
|
if (PropConf.is("headless")) {
|
||||||
|
options.addArguments("--window-size=1920,1080");
|
||||||
|
options.addArguments("--headless");
|
||||||
|
}
|
||||||
|
// see also https://chromium.googlesource.com/chromium/src/+/master/chrome/common/pref_names.cc
|
||||||
|
HashMap<String, Object> chromePrefs = new HashMap<>();
|
||||||
|
chromePrefs.put("profile.default_content_settings.popups", 0);
|
||||||
|
chromePrefs.put("download.default_directory", PropConf.get("download.dir"));
|
||||||
|
chromePrefs.put("download.prompt_for_download", false);
|
||||||
|
options.setExperimentalOption("prefs", chromePrefs);
|
||||||
|
cap.setCapability(ChromeOptions.CAPABILITY, options);
|
||||||
|
return cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
chrome.driver.path=
|
|
||||||
headless=false
|
|
||||||
download.dir=
|
|
Loading…
Reference in new issue