Skip to content

Commit

Permalink
Improved executions summary with errors & screenshots and other UI im…
Browse files Browse the repository at this point in the history
…provements

Signed-off-by: Manoj <[email protected]>
  • Loading branch information
themanojshukla committed Apr 21, 2022
1 parent 8fcea8a commit 2ec4248
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 21 deletions.
40 changes: 40 additions & 0 deletions src/main/java/org/nexial/core/model/ExecutionSummary.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ public class ExecutionSummary {
private Map<String, Map<String, String>> screenRecordings;
private WebServiceLogs wsLogs;

private transient List<StepDetails> stepDetails = new LinkedList<>();

public List<StepDetails> getStepDetails() { return stepDetails; }

private transient boolean stepDetailsHasError = false;

public boolean isStepDetailsHasError(){ return stepDetailsHasError; }

private transient boolean stepDetailsHasLinks = false;

public boolean isStepDetailsHasLinks(){ return stepDetailsHasLinks; }

public void setOutputPath(String outputPath) { this.outputPath = outputPath; }

public static void gatherSupplementProofs(@NotNull ExecutionSummary summary) {
Expand Down Expand Up @@ -376,6 +388,34 @@ public void addNestedMessages(TestStepManifest testStep, List<NestedMessage> nes
nestMessages.put(testStep, nestedTestResults);
}

/**
* Add step details such as row num, description, error messages(if any) and screenshots or linked files to test step
*
* @param step {@link TestStepManifest} provides test step details such as row num and description
* @param nestedMessages list of {@link NestedMessage} containing files and error messages
* @param isSuccess {@link Boolean} to check if test step is passed or not
*/
public void addStepDetails(TestStepManifest step, List<NestedMessage> nestedMessages, boolean isSuccess) {
if (CollectionUtils.isEmpty(nestedMessages)) return;
LinkedList<StepMessage> nestedLinks = new LinkedList<>();
nestedMessages.forEach(nm -> {
String link = (nm instanceof NestedScreenCapture) ? (((NestedScreenCapture) nm).getLink()) : "";
// not adding message if step is passed and no link
if(StringUtils.isBlank(link) && isSuccess) return;
if(StringUtils.isNotBlank(link)){
stepDetailsHasLinks = true;
// not adding message if step is passed
if(isSuccess) nm.setMessage("");
}
nestedLinks.add(new StepMessage(nm.getMessage(), link));
});

if(!isSuccess) { stepDetailsHasError = true; }
if(CollectionUtils.isNotEmpty(nestedLinks)) {
stepDetails.add(new StepDetails(step.rowIndex + 1, step.description, nestedLinks, isSuccess));
}
}

public void addNestSummary(ExecutionSummary nested) { this.nestedExecutions.add(nested); }

public List<ExecutionSummary> getNestedExecutions() { return nestedExecutions; }
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/nexial/core/model/NestedMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public NestedMessage(String message) {

public boolean isPass() { return isPass; }

public void setPass(boolean pass) { isPass = pass; }

public String getResultMessage() { return resultMessage; }

private void processMessage() {
Expand Down
34 changes: 25 additions & 9 deletions src/main/java/org/nexial/core/model/TestStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public class TestStep extends TestStepManifest {
protected List<NestedMessage> nestedTestResults;
protected boolean isCommandRepeater;
protected CommandRepeater commandRepeater;
protected String screenshot;
protected boolean hasErrorScreenshot;
protected boolean isMacroExpander;
protected MacroExecutor macroExecutor;
Expand Down Expand Up @@ -158,6 +159,8 @@ public TestStep(TestCase testCase, List<XSSFCell> row, Worksheet worksheet) {

public Macro getMacro() { return macro; }

public void setScreenshot(String screenshot) { this.screenshot = screenshot; }

@Override
public String toString() {
return new ToStringBuilder(this, SIMPLE_STYLE)
Expand Down Expand Up @@ -611,12 +614,6 @@ protected String handleScreenshot(StepResult result) {
return null;
}

if (result.failed()) {
addErrorScreenCapture(screenshotPath, result.getMessage());
} else {
addStepOutput(screenshotPath, MSG_SCREENCAPTURE);
}

return screenshotPath;
} catch (Exception e) {
error("Unable to capture screenshot: " + e.getMessage());
Expand All @@ -631,9 +628,10 @@ protected void updateResult(StepResult result, long elapsedMs) {
// screenshot
// take care of screenshot requirement first...
// in interactive mode, we won't proceed if any of the Excel-related operations
if (!isSkipped || !isEnded) {
if ((!isSkipped || !isEnded) && StringUtils.isEmpty(screenshot)) {
// don't capture screenshot if step skipped or ended by endIf
handleScreenshot(result);
// OR if screenshot is already captured then also no need of duplicate screenshot
screenshot = handleScreenshot(result);
}

if (!context.isInteractiveMode()) {
Expand Down Expand Up @@ -870,9 +868,27 @@ protected void updateResult(StepResult result, long elapsedMs) {
}
}

updateNestedResults(result);
}

private void updateNestedResults(StepResult result) {
if (StringUtils.isNotEmpty(screenshot)) {
if (result.failed()) {
addErrorScreenCapture(screenshot, result.getMessage());
} else {
addStepOutput(screenshot, MSG_SCREENCAPTURE);
}
} else if (result.isError()) {
addNestedMessage(result.getMessage());
}
// update nestedResult as passed or failed if step has error and screenshot is not specified anywhere
nestedTestResults.forEach(nm -> nm.setPass(result.isSuccess()));

if (CollectionUtils.isNotEmpty(nestedTestResults)) {
TestStepManifest testStep = toTestStepManifest();
testCase.getTestScenario().getExecutionSummary().addNestedMessages(testStep, nestedTestResults);
ExecutionSummary testCaseSummary = testCase.getExecutionSummary();
testCaseSummary.addStepDetails(testStep, nestedTestResults, result.isSuccess());
testCaseSummary.addNestedMessages(testStep, nestedTestResults);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/nexial/core/plugins/base/BaseCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,8 @@ protected String postScreenshot(TestStep testStep, File file) {
return null;
}

testStep.setScreenshot(file.getAbsolutePath());

String caption = context.getStringData(SCREENSHOT_CAPTION);
if (StringUtils.isNotBlank(caption)) {
CaptionModel model = new CaptionModel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ protected File screenshot(String targetFile, WebElement element) {
error("[WARN] Unable to capture screenshot via Winium driver");
return null;
}

context.getCurrentTestStep().setScreenshot(imageFile.getAbsolutePath());
return imageFile;
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/nexial/core/plugins/web/WebCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,7 @@ protected StepResult screenshot(String file, String locator, String timeout, Str

protected StepResult postScreenshot(File target, String locator) throws IOException {
String captured = locator == null ? "FullPage" : "'" + locator + "'";
context.getCurrentTestStep().setScreenshot(target.getAbsolutePath());
if (context.isOutputToCloud()) {
String cloudUrl = context.getOtc().importMedia(target, true);
context.setData(OPT_LAST_OUTPUT_LINK, cloudUrl);
Expand Down Expand Up @@ -2572,7 +2573,7 @@ protected void clearValue(WebElement element) {
String before = element.getAttribute("value");
if (StringUtils.isEmpty(before)) { return; }

// allow user to override "useBackspace" setting; if `nexial.web.clearWithBackspace` is specified
// allow user to override "useBackspace" setting; if `nexial.web.clearWithBackspace` is specified
boolean useBackspace;
if (context.hasData(WEB_CLEAR_WITH_BACKSPACE)) {
useBackspace = context.getBooleanData(WEB_CLEAR_WITH_BACKSPACE);
Expand Down
41 changes: 41 additions & 0 deletions src/main/kotlin/org/nexial/core/model/StepDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* 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 org.nexial.core.model

import org.apache.commons.lang3.StringUtils
import java.util.*

/**
* lightweight version of the [TestStep], without references to POI objects
*/
data class StepDetails(var rowNum: Int, var description: String, var nestedMessages: LinkedList<StepMessage>,
var isPass: Boolean)
data class StepMessage(var message: String, var file: String){
var fileType = "file"
init {
if(StringUtils.isNotEmpty(file)) {
this.fileType = when (file.substringAfterLast('.' ).lowercase()) {
"csv" -> "csv"
"pdf" -> "pdf"
"png", "jpg", "jpeg" -> "image"
"mp4" -> "video"
"xls", "xlsx" -> "excel"
else -> "alt"
}
}
}
}
58 changes: 48 additions & 10 deletions src/main/resources/org/nexial/core/reports/execution_summary.html
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@
<div class="plan-step-summary">
<table cellspacing="0" cellpadding="5" border="0">
<tr data-th-class="${script.failCount > 0 ? 'hasFailure' : 'noFailure'}">
<td class="value value-title" data-th-with="scriptFileName=${@org.nexial.commons.utils.FileUtil@extractFilename(script.scriptFile)}" data-th-attr="title=${scriptFile}">
<a data-th-href="${script.scriptFile}" data-th-utext="${scriptFileName}">myscript.xlsx</a>
<i class="fas fa-copy" data-th-attr="onclick=|copyToClipboard('${#strings.escapeJava(script.scriptFile)}')|" title="copy artifact location"></i>
<td class="value value-title" data-th-with="scriptFileName=${@org.nexial.commons.utils.FileUtil@extractFilename(script.scriptFile)}" title="Click to download script">
<a class="fileLink" data-th-href="${script.scriptFile}" data-th-utext="${scriptFileName}">myscript.xlsx</a>
<i class="fas fa-copy" data-th-attr="onclick=|copyToClipboard('${#strings.escapeJava(script.scriptFile)}')|" title="copy script location"></i>
</td>
<td class="value value-title" data-th-with="dataPath=${script.resolveDataFile()},dataFileName=${@org.nexial.commons.utils.FileUtil@extractFilename(dataPath)}" data-th-attr="title=${dataFileName}">
<a data-th-href="${dataPath}" data-th-utext="${dataFileName}">myscript.data.xlsx</a>
<i class="fas fa-copy" data-th-attr="onclick=|copyToClipboard('${#strings.escapeJava(dataPath)}')|" title="copy artifact location"></i>
<td class="value value-title" data-th-with="dataPath=${script.resolveDataFile()},dataFileName=${@org.nexial.commons.utils.FileUtil@extractFilename(dataPath)}" title="Click to download data">
<a class="fileLink" data-th-href="${dataPath}" data-th-utext="${dataFileName}">myscript.data.xlsx</a>
<i class="fas fa-copy" data-th-attr="onclick=|copyToClipboard('${#strings.escapeJava(dataPath)}')|" title="copy data file location"></i>
</td>
<td class="value value-iteration" data-th-utext="${script.nestedExecutions.size()}">1234</td>
<td class="value value-scenarioPassed" data-th-utext="${script.resolveTotalScenariosPassed()}">1 / 2</td>
Expand Down Expand Up @@ -264,9 +264,9 @@

<th:block data-th-if="${script.nestedExecutions}" data-th-each="iteration : ${script.nestedExecutions}">
<tr data-th-class="'record iter ' + ${iteration.failCount > 0 ? 'hasFailure' : 'noFailure'}">
<td colspan="3" class="value value-title" data-th-attr="title=${iteration.name}" data-th-with="referenceData=${@org.nexial.commons.utils.CollectionUtil@removeEmptyEntries(iteration.referenceData)}">
<a target="_nexial_link" data-th-id="${iteration.testScript.name}" data-th-href="${iteration.testScriptLink}" data-th-utext="${'Execution Result (' + iteration.name + ')'}">myscript.001.xlsx</a>
<i class="fas fa-copy" data-th-attr="onclick=|copyToClipboard('${#strings.escapeJava(iteration.testScriptLink)}')|" title="copy artifact location"></i>
<td colspan="3" class="value value-title" data-th-with="referenceData=${@org.nexial.commons.utils.CollectionUtil@removeEmptyEntries(iteration.referenceData)}">
<a data-th-class="fileLink" title="Click to download result" target="_nexial_link" data-th-id="${iteration.testScript.name}" data-th-href="${iteration.testScriptLink}" data-th-utext="${'Execution Result (' + iteration.name + ')'}">myscript.001.xlsx</a>
<i class="fas fa-copy" data-th-attr="onclick=|copyToClipboard('${#strings.escapeJava(iteration.testScriptLink)}')|" title="copy execution result file location"></i>
<div class="value-scriptRef" data-th-if="${referenceData.size() > 0}">
<i class="fas fa-info-circle info" title="Reference Data labeled as nexial.scriptRef.*"></i>
<table cellspacing="0" cellpadding="0">
Expand Down Expand Up @@ -322,7 +322,20 @@
<th:block data-th-if="${scenario.nestedExecutions}" data-th-each="activity : ${scenario.nestedExecutions}">
<tr data-th-class="'record activity ' + ${activity.failCount > 0 ? 'hasFailure' : 'noFailure'}">
<td colspan="2" class="value spacer2"></td>
<td colspan="2" class="value value-title" data-th-utext="${activity.name}" data-th-attr="title=${activity.name}">Act 1</td>
<td colspan="2" class="value value-title" >
<div class="step-icon-grid" data-th-if="not ${#lists.isEmpty(activity.getStepDetails())}">
<div data-th-attr="title=${activity.name}" data-th-utext="${activity.name}">Act 1</div>
<div>
<i class="fa execution-error-icon" data-th-if="${activity.isStepDetailsHasError()}" onclick="toggleStepErrors(this)" title="View Errors"></i>&nbsp;
</div>
<div>
<i class="fa execution-screenshot-icon" data-th-if="${activity.isStepDetailsHasLinks()}" onclick="toggleStepFiles(this)" title="View Screenshots/Files"></i>
</div>
</div>
<div data-th-if="${#lists.isEmpty(activity.getStepDetails())}">
<span data-th-attr="title=${activity.name}" data-th-utext="${activity.name}">Act 1</span>
</div>
</td>
<td class="value value-datetime exectime" data-th-utext="${#dates.format(new java.util.Date(activity.startTime), 'yyyy/MM/dd HH:mm:ss')}">2018/12/16 12:53:21</td>
<td class="value value-time exectime" data-th-utext="${#dates.format(#dates.create(0,0,0,0,0,0, (activity.endTime - activity.startTime)), 'HH:mm:ss')}">00:42:11</td>
<td class="value value-num passfail" data-th-utext="${#numbers.formatInteger(activity.executed, 1, 'DEFAULT')}">23456</td>
Expand All @@ -332,6 +345,31 @@
data-th-with="success=${activity.executed == 0 ? 0.00 : (activity.passCount * 1.0 / activity.executed) * 100}"
data-th-utext="${#numbers.formatDecimal(success, 1, 2, 'DEFAULT')} + '%'">98.12%</td>
</tr>
<tr class="record canHide step-detail-row" th:with="nestedMessages=${activity.stepDetails}" >
<td class="spacer2"></td>
<td class="spacer2"></td>
<td colspan="8">
<table cellspacing="0" cellpadding="5" style="width:100%">
<th:block data-th-if="${nestedMessages}" data-th-each="messages : ${nestedMessages}" >
<tr class="record step-detail-tr" data-th-attr="is-step-error=${not messages.isPass}" data-th-each="messageMap, msgMapIdx : ${messages.nestedMessages}">
<td class="value step-table-col-width-5" style="text-align: right" data-th-utext="${messages.rowNum}"
data-th-if="${msgMapIdx.index == 0}" data-th-attr="rowspan=${#lists.size(messages.nestedMessages)}">Step No.</td>
<td class="value step-table-col-width-45" data-th-title="${messages.description}" data-th-utext="${#strings.abbreviate(messages.description, 100)}"
data-th-if="${msgMapIdx.index == 0}" data-th-attr="rowspan=${#lists.size(messages.nestedMessages)}">description
</td>
<td class="value step-table-col-width-45" data-th-classappend="${messages.isPass} ? '' : 'hasFailure'">
<span data-th-title="${messageMap.message}" data-th-utext="${#strings.abbreviate(messageMap.message, 200)}"></span>
</td>
<td class="value step-table-col-width-5" data-th-classappend="${messages.isPass} ? '' : 'hasFailure'">
<a data-th-if="not ${#strings.isEmpty(messageMap.file)}" target="_nexial_target" data-th-href="${messageMap.file}">
<i class="fas" data-th-classappend="'file-'+${messageMap.fileType}+'-icon'" data-th-title="'View File'"></i>
</a>
</td>
</tr>
</th:block>
</table>
</td>
</tr>
</th:block>
</th:block>
</th:block>
Expand Down
Loading

0 comments on commit 2ec4248

Please sign in to comment.