If #Around only #Timed annotated method like this:
package ru.fabit.visor.config.aop;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.lang.NonNullApi;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.scheduling.annotation.Scheduled;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* The type Targeted timed aspect.
*/
#Aspect
#NonNullApi
public class TargetedTimedAspect {
public static final String DEFAULT_METRIC_NAME = "method.timed";
public static final String EXCEPTION_TAG = "exception";
public static final String BINDING_TAG = "binding";
public static final String SCHEDULED_CRON_TAG = "cron";
private final MeterRegistry registry;
private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;
public TargetedTimedAspect(MeterRegistry registry) {
this(registry, pjp ->
Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
"method", pjp.getStaticPart().getSignature().getName())
);
}
public TargetedTimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
this.registry = registry;
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
}
// enable TimedAspect only for #StreamListener and #Scheduled annotated methods or allowed methods pointcut
#Around("timedAnnotatedPointcut() )")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
StreamListener streamListener = method.getAnnotation(StreamListener.class);
Scheduled scheduled = method.getAnnotation(Scheduled.class);
// timed can be on method or class
Timed timed = method.getAnnotation(Timed.class);
if (timed == null) {
method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
timed = method.getAnnotation(Timed.class);
}
final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();
Timer.Sample sample = Timer.start(registry);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
Timer.Builder timerBuilder = Timer.builder(metricName)
.description(timed.description().isEmpty() ? null : timed.description())
.tags(timed.extraTags())
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());
if (streamListener != null) {
timerBuilder.tags(
BINDING_TAG,
streamListener.value().isEmpty() ? streamListener.target() : streamListener.value()
);
} else if (scheduled != null) {
timerBuilder.tags(SCHEDULED_CRON_TAG, scheduled.cron());
}
sample.stop(timerBuilder.register(registry));
} catch (Exception e) {
// ignoring on purpose
}
}
}
#Pointcut(
"(#annotation(org.springframework.cloud.stream.annotation.StreamListener) ||" +
"#annotation(org.springframework.scheduling.annotation.Scheduled))"
)
public void asyncAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("execution(public * ru.fabit.visor.service.impl.StorageClientImpl.*(..)) ||" +
"execution(public * ru.fabit.visor.service.s3storage.S3StorageClientImpl.*(..))")
public void allowedMethodPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("#annotation(io.micrometer.core.annotation.Timed)")
public void timedAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
}
Then return: java.lang.IllegalArgumentException: Prometheus requires that all meters with the same name have the same set of tag keys. There is already an existing meter named 'web_photos_gotten_list_seconds' containing tag keys [class, exception, method]. The meter you are attempting to register has keys [exception, method, outcome, status, uri].
But, if add all #Timed method in Pointcut, all good work, i dont understand, why we need all annotated method add to Pointcut separately?
This work:
package ru.fabit.visor.config.aop;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.lang.NonNullApi;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.scheduling.annotation.Scheduled;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* The type Targeted timed aspect.
*/
#Aspect
#NonNullApi
public class TargetedTimedAspect {
public static final String DEFAULT_METRIC_NAME = "method.timed";
public static final String EXCEPTION_TAG = "exception";
public static final String BINDING_TAG = "binding";
public static final String SCHEDULED_CRON_TAG = "cron";
private final MeterRegistry registry;
private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;
public TargetedTimedAspect(MeterRegistry registry) {
this(registry, pjp ->
Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
"method", pjp.getStaticPart().getSignature().getName())
);
}
public TargetedTimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
this.registry = registry;
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
}
// enable TimedAspect only for #StreamListener and #Scheduled annotated methods or allowed methods pointcut
#Around("timedAnnotatedPointcut() && (asyncAnnotatedPointcut() || allowedMethodPointcut())")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
StreamListener streamListener = method.getAnnotation(StreamListener.class);
Scheduled scheduled = method.getAnnotation(Scheduled.class);
// timed can be on method or class
Timed timed = method.getAnnotation(Timed.class);
if (timed == null) {
method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
timed = method.getAnnotation(Timed.class);
}
final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();
Timer.Sample sample = Timer.start(registry);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
Timer.Builder timerBuilder = Timer.builder(metricName)
.description(timed.description().isEmpty() ? null : timed.description())
.tags(timed.extraTags())
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());
if (streamListener != null) {
timerBuilder.tags(
BINDING_TAG,
streamListener.value().isEmpty() ? streamListener.target() : streamListener.value()
);
} else if (scheduled != null) {
timerBuilder.tags(SCHEDULED_CRON_TAG, scheduled.cron());
}
sample.stop(timerBuilder.register(registry));
} catch (Exception e) {
// ignoring on purpose
}
}
}
#Pointcut(
"(#annotation(org.springframework.cloud.stream.annotation.StreamListener) ||" +
"#annotation(org.springframework.scheduling.annotation.Scheduled))"
)
public void asyncAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("execution(public * ru.fabit.visor.service.impl.StorageClientImpl.*(..)) ||" +
"execution(public * ru.fabit.visor.service.s3storage.S3StorageClientImpl.*(..))")
public void allowedMethodPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("#annotation(io.micrometer.core.annotation.Timed)")
public void timedAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
}
pom.xml:
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
The problem youre discribing has nothing to do with pointcuts. There is one piece of code that generates a Timer with three tags (class, exception, method) and another one creating the timer with the exact same same with 5 tags (exception, method, outcome, status, uri) and the framework clearly says, that this is now allowed.
There are several possibilites to solve the issue:
simply use another name for the timer (if you need the other one)
find the other piece of code that generates that timer and deactivate it. Maybe you need to use the debugger and set an conditional breakpoint in MeterRegistry.register()` that registers the meters with the condition that the meter name matches.
PS: using the URI as a tag is not a good practice. The issue is that anyone access your service using different URIs (e.g. by just adding a random number) that will end up in a very high number of meters, which will finally kill your prometheus.
I want to extend functionality of EditableFragmentEntryProcessor in Liferay 7.4 (<lfr-editable> tags in fragments) by searching in text syntaxes like {user.name} and replacing it with value from response from my external API.
e.x.
I type something like
This is super fragment and you are {user.name}.
And result should be
This is super fragment and you are Steven.
I achieve that with creating my own FragmentEntryProcessor, but I did this by putting fragment configuration variable in my custom tag
<my-data-api> ${configuration.testVariable} </my-data-api>
I tried something like this before
<my-data-api>
<lfr-editable id="some-id" type="text">
some text to edit
</lfr-editable>
</my-data-api>
And it doesn't work (and I know why).
So I want to get something like this. Appreciate any help or hints.
EDIT:
Here my custom FragmentEntryProcessor:
package com.example.fragmentEntryProcessorTest.portlet;
import com.example.test.api.api.TestPortletApi;
import com.liferay.fragment.exception.FragmentEntryContentException;
import com.liferay.fragment.model.FragmentEntryLink;
import com.liferay.fragment.processor.FragmentEntryProcessor;
import com.liferay.fragment.processor.FragmentEntryProcessorContext;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.util.Validator;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import java.io.IOException;
/**
* #author kabatk
*/
#Component(
immediate = true, property = "fragment.entry.processor.priority:Integer=100",
service = FragmentEntryProcessor.class
)
public class FragmentEntryProcessorApiDataCopy implements FragmentEntryProcessor {
private static final String _TAG = "my-data-api";
#Reference
private TestPortletApi _api;
#Override
public String processFragmentEntryLinkHTML(
FragmentEntryLink fragmentEntryLink, String html,
FragmentEntryProcessorContext fragmentEntryProcessorContext)
throws PortalException {
Document document = _getDocument(html);
Elements elements = document.getElementsByTag(_TAG);
elements.forEach(
element -> {
String text = element.text();
String attrValue = element.attr("dataType");
String classValues = element.attr("classes");
Element myElement = null;
String result;
try {
result = _api.changeContent(text);
} catch (IOException e) {
e.printStackTrace();
result = "";
}
if(attrValue.equals("img")){
myElement = document.createElement("img");
myElement.attr("class", classValues);
myElement.attr("src", result);
}else if(attrValue.equals("text")){
myElement = document.createElement("div");
myElement.attr("class", classValues);
myElement.html(result);
}
if(myElement != null)
element.replaceWith(myElement);
else
element.replaceWith(
document.createElement("div").text("Error")
);
});
Element bodyElement = document.body();
return bodyElement.html();
}
#Override
public void validateFragmentEntryHTML(String html, String configuration)
throws PortalException {
Document document = _getDocument(html);
Elements elements = document.getElementsByTag(_TAG);
for (Element element : elements) {
if (Validator.isNull(element.attr("dataType"))) {
throw new FragmentEntryContentException("Missing 'dataType' attribute!");
}
}
}
private Document _getDocument(String html) {
Document document = Jsoup.parseBodyFragment(html);
Document.OutputSettings outputSettings = new Document.OutputSettings();
outputSettings.prettyPrint(false);
document.outputSettings(outputSettings);
return document;
}
}
I am copying pasting code from here, the whole file, and then I would like to make changes to it and implement IReporter and I have done all necessary imports in my java file.
All the errors were gone except one related to below import:
import org.testng.internal.Utils;
The error is
The method longStackTrace(Throwable, boolean) is undefined for the type Utils
The method shortStackTrace(Throwable, boolean) is undefined for the type Utils
These two are defined , I can see it here and here . I tried to clean the project , restart eclipse but nothing worked.
in pom.xml
<testng.version>6.10</testng.version>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
<scope>compile</scope> //tried this also
</dependency>
below is the class
package firsttestngpackage;
import java.util.Map;
//import org.apache.xml.serializer.utils.Utils;
import org.testng.IReporter;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.reporters.TestHTMLReporter;
import org.testng.xml.XmlSuite;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.TestListenerAdapter;
import org.testng.TestRunner;
import org.testng.internal.Utils;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class CustomReport implements IReporter {
#Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
String outputDirectory) {
}
public static void generateTable(PrintWriter pw, String title,
Collection<ITestResult> tests, String cssClass, Comparator<ITestResult> comparator)
{
pw.append("<table width='100%' border='1' class='invocation-").append(cssClass).append("'>\n")
.append("<tr><td colspan='4' align='center'><b>").append(title).append("</b></td></tr>\n")
.append("<tr>")
.append("<td><b>Test method</b></td>\n")
.append("<td width=\"30%\"><b>Exception</b></td>\n")
.append("<td width=\"10%\"><b>Time (seconds)</b></td>\n")
.append("<td><b>Instance</b></td>\n")
.append("</tr>\n");
if (tests instanceof List) {
Collections.sort((List<ITestResult>) tests, comparator);
}
// User output?
String id;
Throwable tw;
for (ITestResult tr : tests) {
pw.append("<tr>\n");
// Test method
ITestNGMethod method = tr.getMethod();
String name = method.getMethodName();
pw.append("<td title='").append(tr.getTestClass().getName()).append(".")
.append(name)
.append("()'>")
.append("<b>").append(name).append("</b>");
// Test class
String testClass = tr.getTestClass().getName();
if (testClass != null) {
pw.append("<br>").append("Test class: ").append(testClass);
// Test name
String testName = tr.getTestName();
if (testName != null) {
pw.append(" (").append(testName).append(")");
}
}
// Method description
if (! Utils.isStringEmpty(method.getDescription())) {
pw.append("<br>").append("Test method: ").append(method.getDescription());
}
Object[] parameters = tr.getParameters();
if (parameters != null && parameters.length > 0) {
pw.append("<br>Parameters: ");
for (int j = 0; j < parameters.length; j++) {
if (j > 0) {
pw.append(", ");
}
pw.append(parameters[j] == null ? "null" : parameters[j].toString());
}
}
//
// Output from the method, created by the user calling Reporter.log()
//
{
List<String> output = Reporter.getOutput(tr);
if (null != output && output.size() > 0) {
pw.append("<br/>");
// Method name
String divId = "Output-" + tr.hashCode();
pw.append("\n<a href=\"#").append(divId).append("\"")
.append(" onClick='toggleBox(\"").append(divId).append("\", this, \"Show output\", \"Hide output\");'>")
.append("Show output</a>\n")
.append("\n<a href=\"#").append(divId).append("\"")
.append(" onClick=\"toggleAllBoxes();\">Show all outputs</a>\n")
;
// Method output
pw.append("<div class='log' id=\"").append(divId).append("\">\n");
for (String s : output) {
pw.append(s).append("<br/>\n");
}
pw.append("</div>\n");
}
}
pw.append("</td>\n");
// Exception
tw = tr.getThrowable();
String stackTrace;
String fullStackTrace;
id = "stack-trace" + tr.hashCode();
pw.append("<td>");
if (null != tw) {
fullStackTrace = Utils.longStackTrace(tw, true);
stackTrace = "<div><pre>" + Utils.shortStackTrace(tw, true) + "</pre></div>";
pw.append(stackTrace);
// JavaScript link
pw.append("<a href='#' onClick='toggleBox(\"")
.append(id).append("\", this, \"Click to show all stack frames\", \"Click to hide stack frames\")'>")
.append("Click to show all stack frames").append("</a>\n")
.append("<div class='stack-trace' id='").append(id).append("'>")
.append("<pre>").append(fullStackTrace).append("</pre>")
.append("</div>")
;
}
pw.append("</td>\n");
// Time
long time = (tr.getEndMillis() - tr.getStartMillis()) / 1000;
String strTime = Long.toString(time);
pw.append("<td>").append(strTime).append("</td>\n");
// Instance
Object instance = tr.getInstance();
pw.append("<td>").append(Objects.toString(instance)).append("</td>");
pw.append("</tr>\n");
}
pw.append("</table><p>\n");
}
}
As per suggestion in above comments
updating testNG plugin through eclipse help--> check for updates --> then selecting testNG and updating, resolved the error
I know this was not possible before, but now with the following update:
https://developers.google.com/web/updates/2017/04/devtools-release-notes#screenshots
this seems to be possible using Chrome Dev Tools.
Is it possible now using Selenium in Java?
Yes it possible to take a full page screenshot with Selenium since Chrome v59. The Chrome driver has two new endpoints to directly call the DevTools API:
/session/:sessionId/chromium/send_command_and_get_result
/session/:sessionId/chromium/send_command
The Selenium API doesn't implement these commands, so you'll have to send them directly with the underlying executor. It's not straightforward, but at least it's possible to produce the exact same result as DevTools.
Here's an example with python working on a local or remote instance:
from selenium import webdriver
import json, base64
capabilities = {
'browserName': 'chrome',
'chromeOptions': {
'useAutomationExtension': False,
'args': ['--disable-infobars']
}
}
driver = webdriver.Chrome(desired_capabilities=capabilities)
driver.get("https://stackoverflow.com/questions")
png = chrome_takeFullScreenshot(driver)
with open(r"C:\downloads\screenshot.png", 'wb') as f:
f.write(png)
, and the code to take a full page screenshot :
def chrome_takeFullScreenshot(driver) :
def send(cmd, params):
resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
url = driver.command_executor._url + resource
body = json.dumps({'cmd':cmd, 'params': params})
response = driver.command_executor._request('POST', url, body)
return response.get('value')
def evaluate(script):
response = send('Runtime.evaluate', {'returnByValue': True, 'expression': script})
return response['result']['value']
metrics = evaluate( \
"({" + \
"width: Math.max(window.innerWidth, document.body.scrollWidth, document.documentElement.scrollWidth)|0," + \
"height: Math.max(innerHeight, document.body.scrollHeight, document.documentElement.scrollHeight)|0," + \
"deviceScaleFactor: window.devicePixelRatio || 1," + \
"mobile: typeof window.orientation !== 'undefined'" + \
"})")
send('Emulation.setDeviceMetricsOverride', metrics)
screenshot = send('Page.captureScreenshot', {'format': 'png', 'fromSurface': True})
send('Emulation.clearDeviceMetricsOverride', {})
return base64.b64decode(screenshot['data'])
With Java:
public static void main(String[] args) throws Exception {
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("useAutomationExtension", false);
options.addArguments("disable-infobars");
ChromeDriverEx driver = new ChromeDriverEx(options);
driver.get("https://stackoverflow.com/questions");
File file = driver.getFullScreenshotAs(OutputType.FILE);
}
import java.lang.reflect.Method;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.http.HttpMethod;
public class ChromeDriverEx extends ChromeDriver {
public ChromeDriverEx() throws Exception {
this(new ChromeOptions());
}
public ChromeDriverEx(ChromeOptions options) throws Exception {
this(ChromeDriverService.createDefaultService(), options);
}
public ChromeDriverEx(ChromeDriverService service, ChromeOptions options) throws Exception {
super(service, options);
CommandInfo cmd = new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST);
Method defineCommand = HttpCommandExecutor.class.getDeclaredMethod("defineCommand", String.class, CommandInfo.class);
defineCommand.setAccessible(true);
defineCommand.invoke(super.getCommandExecutor(), "sendCommand", cmd);
}
public <X> X getFullScreenshotAs(OutputType<X> outputType) throws Exception {
Object metrics = sendEvaluate(
"({" +
"width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0," +
"height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0," +
"deviceScaleFactor: window.devicePixelRatio || 1," +
"mobile: typeof window.orientation !== 'undefined'" +
"})");
sendCommand("Emulation.setDeviceMetricsOverride", metrics);
Object result = sendCommand("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", true));
sendCommand("Emulation.clearDeviceMetricsOverride", ImmutableMap.of());
String base64EncodedPng = (String)((Map<String, ?>)result).get("data");
return outputType.convertFromBase64Png(base64EncodedPng);
}
protected Object sendCommand(String cmd, Object params) {
return execute("sendCommand", ImmutableMap.of("cmd", cmd, "params", params)).getValue();
}
protected Object sendEvaluate(String script) {
Object response = sendCommand("Runtime.evaluate", ImmutableMap.of("returnByValue", true, "expression", script));
Object result = ((Map<String, ?>)response).get("result");
return ((Map<String, ?>)result).get("value");
}
}
To do this with Selenium Webdriver in Java takes a bit of work.. As hinted by Florent B. we need to change some classes uses by the default ChromeDriver to make this work. First we need to make a new DriverCommandExecutor which adds the new Chrome commands:
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.service.DriverCommandExecutor;
import org.openqa.selenium.remote.service.DriverService;
public class MyChromeDriverCommandExecutor extends DriverCommandExecutor {
private static final ImmutableMap<String, CommandInfo> CHROME_COMMAND_NAME_TO_URL;
public MyChromeDriverCommandExecutor(DriverService service) {
super(service, CHROME_COMMAND_NAME_TO_URL);
}
static {
CHROME_COMMAND_NAME_TO_URL = ImmutableMap.of("launchApp", new CommandInfo("/session/:sessionId/chromium/launch_app", HttpMethod.POST)
, "sendCommandWithResult", new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST)
);
}
}
After that we need to create a new ChromeDriver class which will then use this thing. We need to create the class because the original has no constructor that lets us replace the command executor... So the new class becomes:
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.html5.LocalStorage;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.html5.LocationContext;
import org.openqa.selenium.html5.SessionStorage;
import org.openqa.selenium.html5.WebStorage;
import org.openqa.selenium.interactions.HasTouchScreen;
import org.openqa.selenium.interactions.TouchScreen;
import org.openqa.selenium.mobile.NetworkConnection;
import org.openqa.selenium.remote.FileDetector;
import org.openqa.selenium.remote.RemoteTouchScreen;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.html5.RemoteLocationContext;
import org.openqa.selenium.remote.html5.RemoteWebStorage;
import org.openqa.selenium.remote.mobile.RemoteNetworkConnection;
public class MyChromeDriver extends RemoteWebDriver implements LocationContext, WebStorage, HasTouchScreen, NetworkConnection {
private RemoteLocationContext locationContext;
private RemoteWebStorage webStorage;
private TouchScreen touchScreen;
private RemoteNetworkConnection networkConnection;
//public MyChromeDriver() {
// this(ChromeDriverService.createDefaultService(), new ChromeOptions());
//}
//
//public MyChromeDriver(ChromeDriverService service) {
// this(service, new ChromeOptions());
//}
public MyChromeDriver(Capabilities capabilities) {
this(ChromeDriverService.createDefaultService(), capabilities);
}
//public MyChromeDriver(ChromeOptions options) {
// this(ChromeDriverService.createDefaultService(), options);
//}
public MyChromeDriver(ChromeDriverService service, Capabilities capabilities) {
super(new MyChromeDriverCommandExecutor(service), capabilities);
this.locationContext = new RemoteLocationContext(this.getExecuteMethod());
this.webStorage = new RemoteWebStorage(this.getExecuteMethod());
this.touchScreen = new RemoteTouchScreen(this.getExecuteMethod());
this.networkConnection = new RemoteNetworkConnection(this.getExecuteMethod());
}
#Override
public void setFileDetector(FileDetector detector) {
throw new WebDriverException("Setting the file detector only works on remote webdriver instances obtained via RemoteWebDriver");
}
#Override
public LocalStorage getLocalStorage() {
return this.webStorage.getLocalStorage();
}
#Override
public SessionStorage getSessionStorage() {
return this.webStorage.getSessionStorage();
}
#Override
public Location location() {
return this.locationContext.location();
}
#Override
public void setLocation(Location location) {
this.locationContext.setLocation(location);
}
#Override
public TouchScreen getTouch() {
return this.touchScreen;
}
#Override
public ConnectionType getNetworkConnection() {
return this.networkConnection.getNetworkConnection();
}
#Override
public ConnectionType setNetworkConnection(ConnectionType type) {
return this.networkConnection.setNetworkConnection(type);
}
public void launchApp(String id) {
this.execute("launchApp", ImmutableMap.of("id", id));
}
}
This is mostly a copy of the original class, but with some constructors disabled (because some of the needed code is package private). If you are in need of these constructors you must place the classes in the package org.openqa.selenium.chrome.
With these changes you are able to call the required code, as shown by Florent B., but now in Java with the Selenium API:
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.Response;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class ChromeExtender {
#Nonnull
private MyChromeDriver m_wd;
public ChromeExtender(#Nonnull MyChromeDriver wd) {
m_wd = wd;
}
public void takeScreenshot(#Nonnull File output) throws Exception {
Object visibleSize = evaluate("({x:0,y:0,width:window.innerWidth,height:window.innerHeight})");
Long visibleW = jsonValue(visibleSize, "result.value.width", Long.class);
Long visibleH = jsonValue(visibleSize, "result.value.height", Long.class);
Object contentSize = send("Page.getLayoutMetrics", new HashMap<>());
Long cw = jsonValue(contentSize, "contentSize.width", Long.class);
Long ch = jsonValue(contentSize, "contentSize.height", Long.class);
/*
* In chrome 61, delivered one day after I wrote this comment, the method forceViewport was removed.
* I commented it out here with the if(false), and hopefully wrote a working alternative in the else 8-/
*/
if(false) {
send("Emulation.setVisibleSize", ImmutableMap.of("width", cw, "height", ch));
send("Emulation.forceViewport", ImmutableMap.of("x", Long.valueOf(0), "y", Long.valueOf(0), "scale", Long.valueOf(1)));
} else {
send("Emulation.setDeviceMetricsOverride",
ImmutableMap.of("width", cw, "height", ch, "deviceScaleFactor", Long.valueOf(1), "mobile", Boolean.FALSE, "fitWindow", Boolean.FALSE)
);
send("Emulation.setVisibleSize", ImmutableMap.of("width", cw, "height", ch));
}
Object value = send("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", Boolean.TRUE));
// Since chrome 61 this call has disappeared too; it does not seem to be necessary anymore with the new code.
// send("Emulation.resetViewport", ImmutableMap.of());
send("Emulation.setVisibleSize", ImmutableMap.of("x", Long.valueOf(0), "y", Long.valueOf(0), "width", visibleW, "height", visibleH));
String image = jsonValue(value, "data", String.class);
byte[] bytes = Base64.getDecoder().decode(image);
try(FileOutputStream fos = new FileOutputStream(output)) {
fos.write(bytes);
}
}
#Nonnull
private Object evaluate(#Nonnull String script) throws IOException {
Map<String, Object> param = new HashMap<>();
param.put("returnByValue", Boolean.TRUE);
param.put("expression", script);
return send("Runtime.evaluate", param);
}
#Nonnull
private Object send(#Nonnull String cmd, #Nonnull Map<String, Object> params) throws IOException {
Map<String, Object> exe = ImmutableMap.of("cmd", cmd, "params", params);
Command xc = new Command(m_wd.getSessionId(), "sendCommandWithResult", exe);
Response response = m_wd.getCommandExecutor().execute(xc);
Object value = response.getValue();
if(response.getStatus() == null || response.getStatus().intValue() != 0) {
//System.out.println("resp: " + response);
throw new MyChromeDriverException("Command '" + cmd + "' failed: " + value);
}
if(null == value)
throw new MyChromeDriverException("Null response value to command '" + cmd + "'");
//System.out.println("resp: " + value);
return value;
}
#Nullable
static private <T> T jsonValue(#Nonnull Object map, #Nonnull String path, #Nonnull Class<T> type) {
String[] segs = path.split("\\.");
Object current = map;
for(String name: segs) {
Map<String, Object> cm = (Map<String, Object>) current;
Object o = cm.get(name);
if(null == o)
return null;
current = o;
}
return (T) current;
}
}
This lets you use the commands as specified, and creates a file with a png format image inside it. You can of course also directly create a BufferedImage by using ImageIO.read() on the bytes.
In Selenium 4, FirefoxDriver provides a getFullPageScreenshotAs method that handles vertical and horizontal scrolling, as well as fixed elements (e.g. navbars). ChromeDriver may implement this method in later releases.
System.setProperty("webdriver.gecko.driver", "path/to/geckodriver");
final FirefoxOptions options = new FirefoxOptions();
// set options...
final FirefoxDriver driver = new FirefoxDriver(options);
driver.get("https://stackoverflow.com/");
File fullScreenshotFile = driver.getFullPageScreenshotAs(OutputType.FILE);
// File will be deleted once the JVM exits, so you should copy it
For ChromeDriver, the selenium-shutterbug library can be used.
Shutterbug.shootPage(driver, Capture.FULL, true).save();
// Take screenshot of the whole page using Chrome DevTools
I have a few lambdas working together through SNS. One lambda receives a request and send data to SNS. Another lambda is subscribed to SNS. It was easy to do in JavaScript as the incoming message just an JS object. Now I am rewriting the lambda to Java. I am looking for the type to use in the handler.
Here's what the lambda looks like. SNSMessage is the placeholder for the type.
public class ArchiveRequestHandler implements RequestHandler<SNSMessage?, Void> {
#Override public Void handleRequest(SNSMessage? input, Context context) {
// do something with the message
return null;
}
}
This is how an example message looks like:
{
"Records": [
{
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
"EventSource": "aws:sns",
"Sns": {
"SignatureVersion": "1",
"Timestamp": "1970-01-01T00:00:00.000Z",
"Signature": "EXAMPLE",
"SigningCertUrl": "EXAMPLE",
"MessageId": "1234567-ee98-5cb9-9903-4c221d41eb5e",
"Message": "Hello from SNS!",
"MessageAttributes": {
"Test": {
"Type": "String",
"Value": "TestString"
},
"TestBinary": {
"Type": "Binary",
"Value": "TestBinary"
}
},
"Type": "Notification",
"UnsubscribeUrl": "EXAMPLE",
"TopicArn": "arn:aws:sns:EXAMPLE",
"Subject": "TestInvoke"
}
}
]
}
Now I am sure I can create my own type to parse this, but I was hoping there is a more standard way of doing this. However I haven't found anything in the lambda nor SNS SDK dependencies that looks like this object.
Thanks to the comments by #dnault and #notionquest I found that this object is part of the aws-lambda-java-events library. I added the dependency:
compile 'com.amazonaws:aws-lambda-java-events:1.3.0'
And now I can do:
public class ArchiveRequestHandler implements RequestHandler<SNSEvent, Void> {
#Override public Void handleRequest(SNSEvent input, Context context) {
// do something with the message
return null;
}
}
The following Lambda code processes the SNS input message/s:
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class LogEvent implements RequestHandler<SNSEvent, Object> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
public Object handleRequest(SNSEvent request, Context context){
String timeStamp = sdf.format(new Date());
LambdaLogger logger = context.getLogger();
logger.log("-------------------------Invocation started: --------------" + timeStamp);
List<SNSEvent.SNSRecord> snsRecordList = request.getRecords();
if ( snsRecordList != null ){
SNSEvent.SNS recordSNS = null;
for ( SNSEvent.SNSRecord snsRecord : snsRecordList ) {
recordSNS = snsRecord.getSNS();
logger.log(
"Subject:[" + recordSNS.getSubject() + "]" +
"Arn:[" + recordSNS.getTopicArn() + "]" +
"attribs:[" + recordSNS.getMessageAttributes() + "]" +
"message:[" + recordSNS.getMessage() + "]" );
}//for
}
timeStamp = sdf.format(new Date());
logger.log("-------------------------Invocation completed: -------------" + timeStamp);
return null;
}
}