Skip to content

Commit

Permalink
Merge pull request #36 from SergeiZagriychuk/stf-client-improvements
Browse files Browse the repository at this point in the history
Feature with saving of session logs
  • Loading branch information
vdelendik authored Feb 11, 2021
2 parents 72d1fac + 54a470a commit a09511f
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 13 deletions.
84 changes: 75 additions & 9 deletions src/main/java/com/zebrunner/mcloud/grid/MobileRemoteProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
Expand All @@ -36,8 +37,10 @@
import org.openqa.grid.internal.TestSlot;
import org.openqa.grid.selenium.proxy.DefaultRemoteProxy;

import com.google.common.collect.ImmutableMap;
import com.zebrunner.mcloud.grid.integration.Appium;
import com.zebrunner.mcloud.grid.integration.client.STFClient;
import com.zebrunner.mcloud.grid.models.appium.LogValue;
import com.zebrunner.mcloud.grid.models.stf.STFDevice;
import com.zebrunner.mcloud.grid.s3.S3Uploader;

Expand All @@ -50,8 +53,15 @@ public class MobileRemoteProxy extends DefaultRemoteProxy {
private static final Logger LOGGER = Logger.getLogger(MobileRemoteProxy.class.getName());
private static final Set<String> recordingSessions = new HashSet<>();

private final static Map<String, String> DEFAULT_LOGS_MAPPING_ANDROID = ImmutableMap.of(
"logcat", "android.log",
"server", "session.log");

private final static Map<String, String> DEFAULT_LOGS_MAPPING_IOS = ImmutableMap.of(
"syslog", "session.log");

private static final String ENABLE_VIDEO = "enableVideo";

private static final String ENABLE_LOG = "enableLog";

private final String URL = System.getenv("STF_URL");
private final String TOKEN = System.getenv("STF_TOKEN");
Expand Down Expand Up @@ -150,7 +160,7 @@ public void afterCommand(TestSession session, HttpServletRequest request, HttpSe

// double check that external key not empty
if (!isRecording(sessionId) && !sessionId.isEmpty() && !"DELETE".equals(request.getMethod())) {
if (isVideoEnabled(session)) {
if (isCapabilityEnabled(session, ENABLE_VIDEO)) {
recordingSessions.add(sessionId);
LOGGER.info("start recording sessionId: " + getExternalSessionId(session));
startRecording(sessionId, session.getSlot().getRemoteURL().toString(), session);
Expand All @@ -167,6 +177,7 @@ public void beforeCommand(TestSession session, HttpServletRequest request, HttpS
// TODO: try to add more conditions to make sure it is DELETE session call
// DELETE /wd/hub/session/5e6960c5-b82b-4e68-a24d-508c3d98dc53
if ("DELETE".equals(request.getMethod())) {
// saving of video recording
boolean isRecording = isRecording(sessionId);
LOGGER.finest("recording for " + sessionId + ": " + isRecording);
if (isRecording) {
Expand All @@ -191,15 +202,22 @@ public void beforeCommand(TestSession session, HttpServletRequest request, HttpS
LOGGER.log(Level.SEVERE, "Error has been occurred during video artifact generation: " + filePath, e);
}

S3Uploader.getInstance().uploadArtifact(sessionId, file);
S3Uploader.getInstance().uploadArtifact(sessionId, file, S3Uploader.VIDEO_S3_FILENAME);
}
else {
LOGGER.log(Level.SEVERE,
"Error has been occurred during termination of video session recording. Video is not saved for session: " + sessionId);
}
}
}

// saving of session logs
boolean isLogEnabled = isCapabilityEnabled(session, ENABLE_LOG);
LOGGER.finest("log saving enabled for " + sessionId + ": " + isLogEnabled);
if (isLogEnabled) {
String appiumUrl = session.getSlot().getRemoteURL().toString();
saveSessionLogsForPlatform(appiumUrl, session);
}
}
}

private boolean isRecording(String sessionId) {
Expand Down Expand Up @@ -253,17 +271,65 @@ private String getExternalSessionId(TestSession session) {
return session.getExternalKey() != null ? session.getExternalKey().getKey() : "";
}

private boolean isVideoEnabled(TestSession session) {
private boolean isCapabilityEnabled(TestSession session, String capabilityName) {
boolean isEnabled = false;
if (session.getRequestedCapabilities().containsKey(ENABLE_VIDEO)) {
isEnabled = (session.getRequestedCapabilities().get(ENABLE_VIDEO) instanceof Boolean)
? (Boolean) session.getRequestedCapabilities().get(ENABLE_VIDEO)
: Boolean.valueOf((String) session.getRequestedCapabilities().get(ENABLE_VIDEO));
if (session.getRequestedCapabilities().containsKey(capabilityName)) {
isEnabled = (session.getRequestedCapabilities().get(capabilityName) instanceof Boolean)
? (Boolean) session.getRequestedCapabilities().get(capabilityName)
: Boolean.valueOf((String) session.getRequestedCapabilities().get(capabilityName));
}

return isEnabled;
}

private void saveSessionLogsForPlatform(String appiumUrl, TestSession session) {
String sessionId = getExternalSessionId(session);
List<String> logTypes = Appium.getLogTypes(appiumUrl, sessionId);
if (logTypes != null) {
if (Platform.ANDROID.equals(Platform.fromCapabilities(session.getRequestedCapabilities()))) {
DEFAULT_LOGS_MAPPING_ANDROID.forEach((k, v) -> {
if(logTypes.contains(k)) {
saveSessionLogs(appiumUrl, sessionId, k, v);
} else {
LOGGER.warning(String.format("Logs of type '%s' are missing for session %s", k.toString(), sessionId));
}
});
} else if (Platform.IOS.equals(Platform.fromCapabilities(session.getRequestedCapabilities()))) {
DEFAULT_LOGS_MAPPING_IOS.forEach((k, v) -> {
if (logTypes.contains(k)) {
saveSessionLogs(appiumUrl, sessionId, k, v);
} else {
LOGGER.warning(String.format("Logs of type '%s' are missing for session %s", k.toString(), sessionId));
}
});
}
}
}

private void saveSessionLogs(String appiumUrl, String sessionId, String logType, String fileName) {
List<LogValue> logs = Appium.getLogs(appiumUrl, sessionId, logType);
if (logs != null && !logs.isEmpty()) {
String locFileName = String.format("%s_%s", sessionId, fileName);
File file = null;
try {
LOGGER.finest("Saving log entries to: " + locFileName);
file = new File(locFileName);
for (LogValue l : logs) {
FileUtils.writeByteArrayToFile(file, l.toString().concat(System.lineSeparator()).getBytes(), true);
}
LOGGER.info("Saved log entries to: " + locFileName);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error has been occurred during log entries saving to " + locFileName, e);
}

try {
S3Uploader.getInstance().uploadArtifact(sessionId, file, fileName);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, String.format("Exception during uploading file '%s' to S3", fileName), e);
}
}
}

private STFClient getSTFClient(Map<String, Object> requestedCapability) {
String token = this.TOKEN;
String timeout = this.TIMEOUT;
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/com/zebrunner/mcloud/grid/integration/Appium.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
*******************************************************************************/
package com.zebrunner.mcloud.grid.integration;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import com.zebrunner.mcloud.grid.integration.client.AppiumClient;
import com.zebrunner.mcloud.grid.models.appium.LogValue;

/**
* Singleton for Appium client.
Expand Down Expand Up @@ -68,4 +71,35 @@ public static String stopRecording(String appiumUrl, String sessionId) {
return videoContent;
}

/**
* Returns logs of chosen type for the sessionId
*
* @param appiumUrl
* @param sessionId
* @param logType
* @return List of found log entries
*/
public static List<LogValue> getLogs(String appiumUrl, String sessionId, String logType) {
List<LogValue> logs = INSTANCE.client.getLogs(appiumUrl, sessionId, logType);
if (logs != null) {
LOGGER.finest(String.format("Found %d log entries of type '%s' for session %s", logs.size(), logType.toString(), sessionId));
}
return logs;
}

/**
* Returns list of available log types for the sessionId
*
* @param appiumUrl
* @param sessionId
* @return List of found log types
*/
public static List<String> getLogTypes(String appiumUrl, String sessionId) {
List<String> logTypes = INSTANCE.client.getAvailableLogTypes(appiumUrl, sessionId);
if (logTypes != null) {
LOGGER.finest(String.format("Log types available for session %s: %s", sessionId, Arrays.toString(logTypes.toArray())));
}
return logTypes;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
package com.zebrunner.mcloud.grid.integration.client;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.zebrunner.mcloud.grid.models.appium.LogTypes;
import com.zebrunner.mcloud.grid.models.appium.LogValue;
import com.zebrunner.mcloud.grid.models.appium.Logs;
import com.zebrunner.mcloud.grid.models.appium.Recording;
import com.zebrunner.mcloud.grid.models.appium.Status;
import com.zebrunner.mcloud.grid.util.HttpClient;
Expand Down Expand Up @@ -48,6 +52,34 @@ public String stopRecordingScreen(String appiumUrl, String sessionId) {
return result;
}

public List<String> getAvailableLogTypes(String appiumUrl, String sessionId) {
Response<LogTypes> response = HttpClient.uri(Path.APPIUM_GET_LOG_TYPES_PATH, appiumUrl, sessionId)
.get(LogTypes.class);

List<String> result = null;
if (response.getStatus() == 200) {
result = response.getObject().getValue();
} else {
LOGGER.log(Level.SEVERE, "Appium response is unsuccessful for get log types call: " + response.getStatus());
}
return result;
}

public List<LogValue> getLogs(String appiumUrl, String sessionId, String logType) {
Map<String, Object> json = new HashMap<>();
json.put("type", logType);
Response<Logs> response = HttpClient.uri(Path.APPIUM_GET_LOGS_PATH, appiumUrl, sessionId)
.post(Logs.class, json);

List<LogValue> result = null;
if (response.getStatus() == 200) {
result = response.getObject().getValue();
} else {
LOGGER.log(Level.SEVERE, String.format("Appium response is unsuccessful for get log call (type=%s): %s", logType, response.getStatus()));
}
return result;
}

public boolean isRunning(String appiumUrl) {
Response<Status> response = HttpClient.uri(Path.APPIUM_STATUS, appiumUrl).get(Status.class);
return response.getStatus() == 200;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright 2018-2021 Zebrunner (https://zebrunner.com/).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/

package com.zebrunner.mcloud.grid.models.appium;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LogTypes {

private List<String> value;
private String sessionId;
private int status;

public enum LogType {
// android
logcat,
bugreport,
// ios
syslog,
crashlog,
performance,
// common
server;
}

@JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}

@JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright 2018-2021 Zebrunner (https://zebrunner.com/).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/

package com.zebrunner.mcloud.grid.models.appium;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LogValue {

private String timestamp;
private String level;
private String message;

@JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}

@JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}

@Override
public String toString() {
return String.format("%s: [%s] %s", timestamp, level, message);
}

}
Loading

0 comments on commit a09511f

Please sign in to comment.