The Zurich release has arrived! Interested in new features and functionalities? Click here for more

Making attachment mandatory in Test Management 2.0

galabborikov
Tera Contributor

Hi everyone, 
I have a requirement to make attachments mandatory in Test Management 2.0 for test steps that are in state 'Blocked' or 'Failed'.
I have tried various customizations to the macro bellow (test_step_list), but none of them are working:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<g2:doctype name="html" />
    <g:inline template="dir_checker.xml" />
    <html ng-app="sn.testManagement" lang="${jvar_text_language}" class="${jvar_text_direction}" style="overflow-y:auto;" data-doctype="true" dir="${jvar_text_direction}">
		<head>
			<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
			<meta name="apple-mobile-web-app-capable" content="yes" />
			<g:inline template="html_page_meta.xml" />
			<link href="${gs.getProperty('glide.product.icon')}" rel="shortcut icon" />
			<!-- JS -->
			<g:inline template="ng_head_inline_script.xml" />
			<g:requires name="scripts/js_includes_test_management.js" includes="true" params="r=$[new global.AgileGlobalUtils().getFlushStamp('jsjsjscache')]"/>
			<g2:evaluate var="jvar_test_result_count">
				var gr = new GlideRecord('sn_test_management_test_run');
				gr.get('$[sysparm_test_run_id]');
				var testCount = gr.getValue('total_tests');
				// Update the run by field to the current user
				gr.setValue('run_by', gs.getUserID());
				gr.update();
				testCount;
			</g2:evaluate>
			<g2:evaluate var="jvar_test_index">
				var index = 0;
				var indexValid = false;
				var totalCount = parseInt('$[jvar_test_result_count]');
				if (!gs.nil('$[sysparm_test_index]')) {
					index = parseInt('$[sysparm_test_index]');
					indexValid = index ${AMP}lt; totalCount;
				}

				if (!indexValid) {
					var lastVisitedTestResult = TestRun.getLastRunTestByCurrentUser('$[sysparm_test_run_id]');
					if (!gs.nil(lastVisitedTestResult))
						index = parseInt(lastVisitedTestResult);

					indexValid = index ${AMP}lt; totalCount;
					if (!indexValid)
						index = 0;
				}
				TestRun.setLastRunTestByCurrentUser('$[sysparm_test_run_id]', index);
				index;
			</g2:evaluate>
			<g2:evaluate var="jvar_test_result" object="true">
				var index = parseInt('$[jvar_test_index]');
				var testResult;
				var gr = new GlideRecord('sn_test_management_test_result');
				gr.addQuery('test_run', '$[sysparm_test_run_id]');
				gr.orderBy('test_version.test.number');
				gr.chooseWindow(index, index + 1, false);
				gr.query();
				if (gr.next())
				testResult = gr;
				testResult;
			</g2:evaluate>
			<g2:evaluate var="jvar_test_steps" object="true">				
				new TestStepResultService().getStepResults('$[jvar_test_result.getValue('sys_id')]');
			</g2:evaluate>
			<g2:evaluate var="jvar_test" object="true">
				var test = new GlideRecord('sn_test_management_test_version');
				test.get('$[jvar_test_result.getValue('test_version')]');
				test;
			</g2:evaluate>
			<g2:evaluate var="jvar_test_position_text">
				var position = String(parseInt('$[jvar_test_index]') + 1);
				var count = '$[jvar_test_result_count]';
				var text = gs.getMessage('{0} out of {1}', [position, count]);
				text;
			</g2:evaluate>
			<g2:evaluate var="jvar_test_case_migrated">
				var migrated = false;
				var testVersionGr = new GlideRecord('sn_test_management_test_version');
				var testCaseGr = new GlideRecord('tm_test_case');
				if(testVersionGr.isValidField('tm_test_case') &amp;&amp; testCaseGr.get('$[jvar_test.getValue('tm_test_case')]')) {
					migrated = testCaseGr.getValue('migrated') === "1" ? true : false;
				}
				migrated;
			</g2:evaluate>
		</head>

		<body>
			<now-message key="File not downloaded" value="${gs.getMessage('The file {0} did not pass security scan and cannot be downloaded.')}"></now-message>
			<j2:if test="$[jvar_test_result_count &lt;= 0]">
				<div class="test-run-error">
					<span class="icon icon-ellipsis"></span>
					<span class="test-run-error-message">
						$[gs.getMessage('This run contains no tests')]
					</span>
				</div>
			</j2:if>
			<j2:if test="$[jvar_test_result_count &gt; 0]">
				<script type="text/ng-template" id="pause_test_dialog.html">
					<g:inline template="sn_test_management_pause_test_dialog.xml" />
				</script>
				<script>
				(function(){
					document.on('click', 'a[data-type="list2_popup"]', function(evt, element) {
        				var showOpenButton = false;
        				var trapFocus = false;
						var view = "migration";
        				popListDiv(evt, "tm_test_case", "$[jvar_test.getValue('tm_test_case')]", "migration", 450, showOpenButton, trapFocus);
        				evt.stop();
    				});
				})();
			</script>
				<div ng-controller="testExecutionCtrl as txc" ng-cloak="">
					<script>
						 angular.module("sn.testManagement").run(function(testManagementData){
							testManagementData.load("testSteps", JSON.parse("$[JS:jvar_test_steps]"));
							testManagementData.load("testIndex", "$[JS:jvar_test_index]");
							testManagementData.load("testResultCount", "$[JS:jvar_test_result_count]");
						});
					</script>
					<input type="hidden" id="test_run_id" name="test_run_id" value="$[jvar_test_run_id]" />
					<nav class="nav navbar-default" >
						<a href="#" ng-if="$[jvar_test_case_migrated]" class="btn btn-icon table-btn-lg icon-info list_popup" data-type="list2_popup" data-list_id="tm_test_case" aria-label="${gs.getMessage('Preview record')}" data-use-href="true" data-popover-title-is-html="false" title="" aria-haspop="true" role="button" aria-expanded="false" data-original-title="${gs.getMessage('Preview record')}"></a>
						<span uib-tooltip="$[jvar_test.getValue('short_description')]" tooltip-placement="bottom-left" tooltip-append-to-body="true" class="test-description text-ellipsis">$[jvar_test.getValue('short_description')]
						</span>
					</nav>
					<form name="step_list" novalidate="">
					<div ng-if="txc.testSteps.length === 0" class="test-run-error">
						<span class="icon icon-ellipsis"></span>
						<span class="test-run-error-message">
							$[gs.getMessage('This test is not available')]
						</span>
					</div>
					<ul ng-if="txc.testSteps.length > 0" class="test_steps">
						<li ng-repeat = "step in txc.testSteps">
							<div class="flex-box flex-box-v-center padding_test_step">
								<div class="step-desc">{{step.step}}</div>
								<div class="step-icon-container" ng-if="step.needs_verification == 1" ng-init="txc.setInitialStepResultStatus(step)">
									<i role="button" class="step-icon icon-workflow-complete" ng-class="step.status === 'passed' ? 'test-passed' : 'text-light'" alt="" ng-click="txc.setStepStatus(step, 'PASSED')" tabindex="0" uib-tooltip="${HTML: gs.getMessage('Passed')}" tooltip-append-to-body="true" aria-label="${gs.getMessage('Mark as passed')}"/>
									<i role="button" class="step-icon icon-workflow-rejected" ng-class="step.status === 'failed' ? 'test-failed' : 'text-light'" alt="" ng-click="txc.setStepStatus(step, 'FAILED')" tabindex="0" uib-tooltip="${HTML: gs.getMessage('Failed')}" tooltip-append-to-body="true" aria-label="${gs.getMessage('Mark as failed')}"/>
									<i role="button" class="step-icon icon-workflow-late" ng-class="step.status === 'blocked' ? 'test-blocked' : 'text-light'" alt="" ng-click="txc.setStepStatus(step, 'BLOCKED')" tabindex="0" uib-tooltip="${HTML: gs.getMessage('Blocked')}" tooltip-append-to-body="true" aria-label="${gs.getMessage('Mark as blocked')}"/>
								</div>
							</div>
							<div class="step_comment_container padding_test_step">
								<div class="step_comment" uib-collapse="!(step.status == 'blocked')">
									<label>${gs.getMessage('Comment')}</label>
									<textarea class="form-control" name="{{step.id}}" id="{{step.id}}" ng-if="step.status == 'blocked'" ng-model-options="{allowInvalid: true}" resize-content="" ng-change="txc.updateComment(step)" ng-model="step.comment" aria-label="${gs.getMessage('Comment')}"></textarea>
								</div>
								<div class="step_comment" uib-collapse="!(step.status == 'failed')">
									<label>${gs.getMessage('Comment')}</label>
									<textarea class="form-control" name="{{step.id}}" id="{{step.id}}" ng-if="step.status == 'failed'" ng-model-options="{allowInvalid: true}" resize-content="" ng-change="txc.updateComment(step)" ng-model="step.comment" aria-label="${gs.getMessage('Comment')}"></textarea>
								</div>
								<div class="step_side_line" ng-class="{'blocked': step.status == 'blocked', 'failed': step.status == 'failed'}"></div>
							</div>
							<div class="step_attachment_container" ng-if="step.status == 'blocked' ||  step.status == 'failed'">
								<div class="add_attachment flex-box flex-box-v-center padding_test_step pull-left" role="button" tabindex="0" upload-file="">
									<span class="icon-add-circle-empty flex-box flex-box-v-center flex-box-h-center"></span>
									<span>${gs.getMessage('Add Attachment')}</span>
								</div>
								<div class="clearfix"></div>
								<input class="upload" type="file" multiple="" ng-file-select="txc.uploadFile(step, $files)" style="display: none;" />
								<ul class="step_attachment_list">
									<li ng-repeat="file in step.files track by file.sys_id" class="flex-box flex-box-v-center step_attachment_item_container" ng-click="txc.handleAttachmentClick($event,file)" ng-class="{'infected_file': file.state === 'not_available'}">
										<span class="step_attachment_image" ng-if="file.image $[AMP]$[AMP] !file.progress" ng-style="::{'background-image': 'url(' + file.thumbSrc + ')'}" />
										<span class="step_attachment_icon" ng-if="!file.image $[AMP]$[AMP] !file.progress" ng-switch="" on="file.ext">
											<span ng-switch-when="pdf" class="{{::txc.fileIcons.pdf}}"></span>
											<span ng-switch-when="doc" class="{{::txc.fileIcons.doc}}"></span>
											<span ng-switch-when="docx" class="{{::txc.fileIcons.doc}}"></span>
											<span ng-switch-when="ppt" class="{{::txc.fileIcons.ppt}}"></span>
											<span ng-switch-when="txt" class="{{::txc.fileIcons.txt}}"></span>
											<span ng-switch-when="xls" class="{{::txc.fileIcons.xls}}"></span>
											<span ng-switch-when="zip" class="{{::txc.fileIcons.zip}}"></span>
											<span ng-switch-default="" class="icon-document"></span>
										</span>
										<div class="step_attachment_detail_container flex-box flex-box-v-center" ng-if="!file.progress">
											<div class="step_attachment_item_detail flex-box">
												<div class="file_name_text flex-ellipsis">
													<span title="{{::file.file_name}}">{{::file.file_name}}</span>
													<span ng-if="::file.state === 'not_available'">$[SP][${gs.getMessage('Unavailable')}]</span>
												</div>
												<div class="file_size_text">{{::file.size}}</div>
											</div>
											<div class="step_attachment_actions">
												<div class="step_attachment_icon_container flex-box">
													<a ng-if="::file.state !== 'not_available'" ng-click="$event.stopPropagation();" class="btn btn-icon icon-download flex-box flex-box-v-center flex-box-h-center" ng-href="{{txc.getDownloadLink(file)}}" title="${HTML: gs.getMessage('Download')}" role="button"></a>
													<span class="btn btn-icon icon-delete flex-box flex-box-v-center flex-box-h-center" title="${gs.getMessage('Delete')}" role="button" ng-click="txc.deleteAttachment($event, step,file,$index)"></span>
												</div>
											</div>
										</div>
										<uib-progressbar ng-if="file.progress" value="file.progress"></uib-progressbar>
									</li>
								</ul>
							</div>
						</li>
					</ul>
						<div class='test_step_actions flex-box flex-box-v-center' ng-class="(txc.testResultCount > 1) ? 'flex-box-content-space-between' : 'flex-box-content-flex-end'">
							<div class="test_step_position_text" ng-if="txc.testResultCount > 1">
								$[jvar_test_position_text]
							</div>
							<div class='test_step_form_button'>
								<button type="button" class="btn btn-default pause-button" ng-click="txc.pauseTestExecution()">${gs.getMessage('Pause')}</button>
								<button type="button" class="btn" ng-class="txc.doneButton.classes" ng-click="txc.completeTestExecution($event,step_list, '$[sysparm_test_run_id]')" data-error="${gs.getMessage('Please add comments to failed or blocked steps')}">${gs.getMessage('Done')}</button>
							</div>
							<div class='test_step_navigate_buttons' ng-if="txc.testResultCount > 1">
								<a class="btn btn-default btn-previous-test icon-chevron-left" tabindex="0" role="button" uib-tooltip="${gs.getMessage('Previous Test')}" tooltip-append-to-body="true" tooltip-placement="auto" aria-label="${gs.getMessage('Go to previous test')}" ng-href="{{txc.prevButton.href}}" ng-disabled="txc.prevButton.disabled"></a>
								<a class="btn btn-primary btn-next-test icon-chevron-right" tabindex="0" role="button" uib-tooltip="${gs.getMessage('Next Test')}" tooltip-append-to-body="true" tooltip-placement="auto" aria-label="${gs.getMessage('Go to next test')}" ng-href="{{txc.nextButton.href}}" ng-disabled="txc.nextButton.disabled"></a>
							</div>
						</div>	
					</form>
				</div>
			</j2:if>
		</body>
	</html>
</j:jelly>

Any help or tips will be appreciated. Thanks

5 REPLIES 5

laloothadhani
Tera Contributor

Hi,

 

Did you manage to accomplish this? I am interested to know the solution.

 

Thanks.

Hi, 


Yes I did. I also did some other modifications (to make the comments mandatory for Passed steps) but this is the final version (most of the new logic is the <script> tag in the body (in the div ng-controller="testExecutionCtrl as txc"): 

<?xml version="1.0" encoding="UTF-8"?>
<j:jelly xmlns:j="jelly:core" xmlns:g="glide" xmlns:g2="null" xmlns:j2="null" trim="false">
   <g2:doctype name="html" />
   <g:inline template="dir_checker.xml" />
   <html ng-app="sn.testManagement" lang="${jvar_text_language}" class="${jvar_text_direction}" style="overflow-y:auto;" data-doctype="true" dir="${jvar_text_direction}">
      <head>
         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
         <meta name="apple-mobile-web-app-capable" content="yes" />
         <g:inline template="html_page_meta.xml" />
         <link href="${gs.getProperty('glide.product.icon')}" rel="shortcut icon" />
         <!-- JS -->
         <g:inline template="ng_head_inline_script.xml" />
         <g:requires name="scripts/js_includes_test_management.js" includes="true" params="r=$[new global.AgileGlobalUtils().getFlushStamp('jsjsjscache')]" />
         <g2:evaluate var="jvar_test_result_count">var gr = new GlideRecord('sn_test_management_test_run');
				gr.get('$[sysparm_test_run_id]');
				var testCount = gr.getValue('total_tests');
				// Update the run by field to the current user
				gr.setValue('run_by', gs.getUserID());
				gr.update();
				testCount;</g2:evaluate>
         <g2:evaluate var="jvar_test_index">var index = 0;
				var indexValid = false;
				var totalCount = parseInt('$[jvar_test_result_count]');
				if (!gs.nil('$[sysparm_test_index]')) {
					index = parseInt('$[sysparm_test_index]');
					indexValid = index ${AMP}lt; totalCount;
				}

				if (!indexValid) {
					var lastVisitedTestResult = TestRun.getLastRunTestByCurrentUser('$[sysparm_test_run_id]');
					if (!gs.nil(lastVisitedTestResult))
						index = parseInt(lastVisitedTestResult);

					indexValid = index ${AMP}lt; totalCount;
					if (!indexValid)
						index = 0;
				}
				TestRun.setLastRunTestByCurrentUser('$[sysparm_test_run_id]', index);
				index;</g2:evaluate>
         <g2:evaluate var="jvar_test_result" object="true">var index = parseInt('$[jvar_test_index]');
				var testResult;
				var gr = new GlideRecord('sn_test_management_test_result');
				gr.addQuery('test_run', '$[sysparm_test_run_id]');
				gr.orderBy('test_version.test.number');
				gr.chooseWindow(index, index + 1, false);
				gr.query();
				if (gr.next())
				testResult = gr;
				testResult;</g2:evaluate>
         <g2:evaluate var="jvar_test_steps" object="true">new TestStepResultService().getStepResults('$[jvar_test_result.getValue('sys_id')]');</g2:evaluate>
         <g2:evaluate var="jvar_test" object="true">var test = new GlideRecord('sn_test_management_test_version');
				test.get('$[jvar_test_result.getValue('test_version')]');
				test;</g2:evaluate>
         <g2:evaluate var="jvar_test_position_text">var position = String(parseInt('$[jvar_test_index]') + 1);
				var count = '$[jvar_test_result_count]';
				var text = gs.getMessage('{0} out of {1}', [position, count]);
				text;</g2:evaluate>
         <g2:evaluate var="jvar_test_case_migrated">var migrated = false;
				var testVersionGr = new GlideRecord('sn_test_management_test_version');
				var testCaseGr = new GlideRecord('tm_test_case');
				if(testVersionGr.isValidField('tm_test_case') &amp;&amp; testCaseGr.get('$[jvar_test.getValue('tm_test_case')]')) {
					migrated = testCaseGr.getValue('migrated') === "1" ? true : false;
				}
				migrated;</g2:evaluate>
      </head>
      <body>
         <now-message key="File not downloaded" value="${gs.getMessage('The file {0} did not pass security scan and cannot be downloaded.')}" />
         <j2:if test="$[jvar_test_result_count &lt;= 0]">
            <div class="test-run-error">
               <span class="icon icon-ellipsis" />
               <span class="test-run-error-message">$[gs.getMessage('This run contains no tests')]</span>
            </div>
         </j2:if>
         <j2:if test="$[jvar_test_result_count &gt; 0]">
            <script type="text/ng-template" id="pause_test_dialog.html">
               <g:inline template="sn_test_management_pause_test_dialog.xml" />
            </script>
            <script>(function(){
					document.on('click', 'a[data-type="list2_popup"]', function(evt, element) {
        				var showOpenButton = false;
        				var trapFocus = false;
						var view = "migration";
        				popListDiv(evt, "tm_test_case", "$[jvar_test.getValue('tm_test_case')]", "migration", 450, showOpenButton, trapFocus);
        				evt.stop();
    				});
				})();</script>
            <div ng-controller="testExecutionCtrl as txc" ng-cloak="">
               <script>angular.module("sn.testManagement").run(function(testManagementData){
							testManagementData.load("testSteps", JSON.parse("$[JS:jvar_test_steps]"));
							testManagementData.load("testIndex", "$[JS:jvar_test_index]");
							testManagementData.load("testResultCount", "$[JS:jvar_test_result_count]");
							checkAttachment = function(event){
							var counter = 0;	
							var stepsLimit = testManagementData.get("testSteps").length;
							testManagementData.get("testSteps").forEach(function(step) {
						
								if(step.status === 'passed'){
									
									counter += 1;
									if (counter == stepsLimit){
									var div = document.getElementById('doneButton');    
									div.style.display ="inline-block";
									var div2 = document.getElementById('testButton');
									div2.style.display ="none";

}
								}								
								if (step.status === 'blocked' || step.status === 'failed'){ 		
								console.log(typeof(step.files.length));
									if (step.files.length &gt; 0) {
										var div = document.getElementById('doneButton');    
										div.style.display ="inline-block";
										var div2 = document.getElementById('testButton');
										div2.style.display ="none";
									}
						}			
							 })								
							}
							checkStatus = function(event){
								
									var div = document.getElementById('doneButton');   
									console.log('CheckStatus style ' + div.style.display); 
									if (div.style.display == "inline-block")
									{
										div = document.getElementById('doneButton');    
										div.style.display ="none";
										var div2 = document.getElementById('testButton');
										div2.style.display ="inline-block";										
									}
							}
							document.getElementById('testButton').addEventListener('click', checkAttachment);
						});</script>
               <input type="hidden" id="test_run_id" name="test_run_id" value="$[jvar_test_run_id]" />
               <nav class="nav navbar-default">
                  <a href="#" ng-if="$[jvar_test_case_migrated]" class="btn btn-icon table-btn-lg icon-info list_popup" data-type="list2_popup" data-list_id="tm_test_case" aria-label="${gs.getMessage('Preview record')}" data-use-href="true" data-popover-title-is-html="false" title="" aria-haspop="true" role="button" aria-expanded="false" data-original-title="${gs.getMessage('Preview record')}" />
                  <span uib-tooltip="$[jvar_test.getValue('short_description')]" tooltip-placement="bottom-left" tooltip-append-to-body="true" class="test-description text-ellipsis">$[jvar_test.getValue('short_description')]</span>
               </nav>
               <form name="step_list" novalidate="">
                  <div ng-if="txc.testSteps.length === 0" class="test-run-error">
                     <span class="icon icon-ellipsis" />
                     <span class="test-run-error-message">$[gs.getMessage('This test is not available')]</span>
                  </div>
                  <ul ng-if="txc.testSteps.length &gt; 0" class="test_steps">
                     <li ng-repeat="step in txc.testSteps">
                        <div class="flex-box flex-box-v-center padding_test_step">
                           <div class="step-desc">{{step.step}}</div>
                           <div class="step-icon-container" ng-if="step.needs_verification == 1" ng-init="txc.setInitialStepResultStatus(step)">
                              <i role="button" class="step-icon icon-workflow-complete" ng-class="step.status === 'passed' ? 'test-passed' : 'text-light'" alt="" ng-click="txc.setStepStatus(step, 'PASSED')" tabindex="0" uib-tooltip="${HTML: gs.getMessage('Passed')}" tooltip-append-to-body="true" aria-label="${gs.getMessage('Mark as passed')}" />
                              <i onclick="checkStatus()" role="button" class="step-icon icon-workflow-rejected" ng-class="step.status === 'failed' ? 'test-failed' : 'text-light'" alt="" ng-click="txc.setStepStatus(step, 'FAILED')" tabindex="0" uib-tooltip="${HTML: gs.getMessage('Failed')}" tooltip-append-to-body="true" aria-label="${gs.getMessage('Mark as failed')}" />
                              <i onclick="checkStatus()" role="button" class="step-icon icon-workflow-late" ng-class="step.status === 'blocked' ? 'test-blocked' : 'text-light'" alt="" ng-click="txc.setStepStatus(step, 'BLOCKED')" tabindex="0" uib-tooltip="${HTML: gs.getMessage('Blocked')}" tooltip-append-to-body="true" aria-label="${gs.getMessage('Mark as blocked')}" />
                           </div>
                        </div>
                        <div class="step_comment_container padding_test_step">
                           <div class="step_comment" uib-collapse="!(step.status == 'blocked')">
                              <label>${gs.getMessage('Comment')}</label>
                              <textarea class="form-control" name="{{step.id}}" id="{{step.id}}" ng-if="step.status == 'blocked'" ng-model-options="{allowInvalid: true}" resize-content="" ng-change="txc.updateComment(step)" ng-model="step.comment" aria-label="${gs.getMessage('Comment')}" />
                           </div>
                           <div class="step_comment" uib-collapse="!(step.status == 'failed')">
                              <label>${gs.getMessage('Comment')}</label>
                              <textarea class="form-control" name="{{step.id}}" id="{{step.id}}" ng-if="step.status == 'failed'" ng-model-options="{allowInvalid: true}" resize-content="" ng-change="txc.updateComment(step)" ng-model="step.comment" aria-label="${gs.getMessage('Comment')}" />
                           </div>
                           <div class="step_side_line" ng-class="{'blocked': step.status == 'blocked', 'failed': step.status == 'failed'}" />
                        </div>
                        <div class="step_attachment_container" ng-if="step.status == 'blocked' || step.status == 'failed'">
                           <div class="add_attachment flex-box flex-box-v-center padding_test_step pull-left" role="button" tabindex="0" upload-file="">
                              <span class="icon-add-circle-empty flex-box flex-box-v-center flex-box-h-center" />
                              <span>${gs.getMessage('Add Attachment')}</span>
                           </div>
                           <div class="clearfix" />
                           <input class="upload" type="file" multiple="" ng-file-select="txc.uploadFile(step, $files)" style="display: none;" />
                           <ul class="step_attachment_list">
                              <li ng-repeat="file in step.files track by file.sys_id" class="flex-box flex-box-v-center step_attachment_item_container" ng-click="txc.handleAttachmentClick($event,file)" ng-class="{'infected_file': file.state === 'not_available'}">
                                 <span class="step_attachment_image" ng-if="file.image &amp;&amp; !file.progress" ng-style="::{'background-image': 'url(' + file.thumbSrc + ')'}" />
                                 <span class="step_attachment_icon" ng-if="!file.image &amp;&amp; !file.progress" ng-switch="" on="file.ext">
                                    <span ng-switch-when="pdf" class="{{::txc.fileIcons.pdf}}" />
                                    <span ng-switch-when="doc" class="{{::txc.fileIcons.doc}}" />
                                    <span ng-switch-when="docx" class="{{::txc.fileIcons.doc}}" />
                                    <span ng-switch-when="ppt" class="{{::txc.fileIcons.ppt}}" />
                                    <span ng-switch-when="txt" class="{{::txc.fileIcons.txt}}" />
                                    <span ng-switch-when="xls" class="{{::txc.fileIcons.xls}}" />
                                    <span ng-switch-when="zip" class="{{::txc.fileIcons.zip}}" />
                                    <span ng-switch-default="" class="icon-document" />
                                 </span>
                                 <div class="step_attachment_detail_container flex-box flex-box-v-center" ng-if="!file.progress">
                                    <div class="step_attachment_item_detail flex-box">
                                       <div class="file_name_text flex-ellipsis">
                                          <span title="{{::file.file_name}}">{{::file.file_name}}</span>
                                          <span ng-if="::file.state === 'not_available'">${gs.getMessage('Unavailable')}</span>
                                       </div>
                                       <div class="file_size_text">{{::file.size}}</div>
                                    </div>
                                    <div class="step_attachment_actions">
                                       <div class="step_attachment_icon_container flex-box">
                                          <a ng-if="::file.state !== 'not_available'" ng-click="$event.stopPropagation();" class="btn btn-icon icon-download flex-box flex-box-v-center flex-box-h-center" ng-href="{{txc.getDownloadLink(file)}}" title="${HTML: gs.getMessage('Download')}" role="button" />
                                          <span class="btn btn-icon icon-delete flex-box flex-box-v-center flex-box-h-center" title="${gs.getMessage('Delete')}" role="button" ng-click="txc.deleteAttachment($event, step, file, $index)" />
                                       </div>
                                    </div>
                                 </div>
                                 <uib-progressbar ng-if="file.progress" value="file.progress" />
                              </li>
                           </ul>
						   
                           <span ng-if="step.status == 'blocked' || step.status == 'failed'" class="error-message" style="color: red;" ng-show="!step.files || step.files.length === 0">${gs.getMessage('    Attachment is required for blocked or failed steps.')}</span>
                        </div>
                     </li>
                  </ul>
				  <div style="margin-top: 20px;">
   <p>Sample message</p>
</div>
                  <div class="test_step_actions flex-box flex-box-v-center" ng-class="(txc.testResultCount &gt; 1) ? 'flex-box-content-space-between' : 'flex-box-content-flex-end'">
                     <div class="test_step_position_text" ng-if="txc.testResultCount &gt; 1">$[jvar_test_position_text]</div>
                     <div class="test_step_form_button">
                        <button type="button" class="btn btn-default pause-button" ng-click="txc.pauseTestExecution()">${gs.getMessage('Pause')}</button>
                        <button id="doneButton" style="display:none" type="button" class="btn" ng-class="txc.doneButton.classes" ng-click="txc.completeTestExecution($event,step_list, '$[sysparm_test_run_id]')" data-error="${gs.getMessage('Please add comments to failed or blocked steps')}">${gs.getMessage('Done')}</button>
                        <button id="testButton" type="button" ng-class="txc.doneButton.classes" class="btn">${gs.getMessage('Validate')}</button>
                     </div>
                     <div class="test_step_navigate_buttons" ng-if="txc.testResultCount &gt; 1">
                        <a class="btn btn-default btn-previous-test icon-chevron-left" tabindex="0" role="button" uib-tooltip="${gs.getMessage('Previous Test')}" tooltip-append-to-body="true" tooltip-placement="auto" aria-label="${gs.getMessage('Go to previous test')}" ng-href="{{txc.prevButton.href}}" ng-disabled="txc.prevButton.disabled" />
                        <a class="btn btn-primary btn-next-test icon-chevron-right" tabindex="0" role="button" uib-tooltip="${gs.getMessage('Next Test')}" tooltip-append-to-body="true" tooltip-placement="auto" aria-label="${gs.getMessage('Go to next test')}" ng-href="{{txc.nextButton.href}}" ng-disabled="txc.nextButton.disabled" />
                     </div>
                  </div>
               </form>
            </div>
         </j2:if>
      </body>
   </html>
</j:jelly>

 

, line 88):

Edvin-Karalius
Tera Guru

Hoooollllyyy... talk about over engineering a problem.
Why not just do a BR to check for attachments at that state and return an error message aborting the action if the user has not added an attachment?

We tried but you cannot stop the submission of the form which is done by the angular function txc.completeTestExecution which is not accessible on ServiceNow.