How to Highlight Test object in each and every step

I love this idea and I praise kazurayam’s ingeniously creative implementation, whose approach will help us solve many more challenges in the future that seemed unthinkable until now, many thanks kaz!

For example, I had long been looking for a way to determine the actual root cause message that is generated when test cases called by WebUI.callTestCase() fail. Because I wanted to send a helpful alarm notification to the responsible colleagues at runtime and not have to wait for the generation and manual analysis of the report (see my original post here). Unfortunately, this root cause message generated during a concrete test step within the nested child test case is not passed on to the parent test case (the one with the WebUI.callTestCase() command). Instead, callTestCase() generates its own, less helpful error notification, which only states that the execution of the child test case failed.

But thanks to kazurayam’s approach, I can now finally catch this original error message at the right moment, without having to overload every single test case with a try-catch block, and write it into a global variable. And not only that: I could easily extend kazurayam’s code to cache all relevant information about the circumstances of the error event in a structured way (keywordName, testObject, testObjectString, inputParams, webElements - see below in the extended pandemic() method) to access them in the parent test case and finally send a meaningful alarm notification with screenshot. Even if you don’t use callTestCase(), with this you can save yourself try-catch blocks in test cases at all!

Finally, I varied the color assignments for the web elements so that you can see at a glance which web element the test case actually failed for, both in the screenshot and in a recorded video. I now distinguish between the following cases and methods, which replace kazurayam’s on() method for me: current (orange coloring), succes (green coloring), failure (red coloring). If a web element is not found at all, it can of course not be colored, but if there is no orange outlined element either, you also know that the error must have occurred after the last green colored element. The supplied detailed data will then only give the last confirmation.

I hope it helps others, too.

public class HighlightElement {

	@Keyword
	public static current(TestObject testObject) {
		return influence(testObject, 'current')
	}

	@Keyword
	public static success(TestObject testObject) {
		return influence(testObject, 'success')
	}

	@Keyword
	public static failure(TestObject testObject) {
		return influence(testObject, 'failure')
	}

	private static influence(TestObject testObject, String status) {
		List<WebElement> elements
		try {
			WebDriver driver = DriverFactory.getWebDriver()
			elements = WebUiCommonHelper.findWebElements(testObject, 5)
			for (WebElement element : elements) {
				JavascriptExecutor js = (JavascriptExecutor) driver
				js.executeScript(
						"arguments[0].setAttribute('style','outline: dashed ${status == 'current' ? 'orange' : (status == 'success' ? 'lime' : 'red')};');",
						element)
			}
		} catch (Exception e) {
			// TODO use Katalon Logging
			e.printStackTrace()
		}
		finally {
			return elements
		}
	}

	private static List<String> influencedKeywords = ['click', 'selectOptionByIndex', 'selectOptionByLabel', 'selectOptionByValue', 'setEncryptedText', 'setText']

	/**
	 * change some of methods of WebUiBuiltInKeywords so that they call HighlightElement.on(testObject)
	 * before invoking their original method body.
	 *
	 * http://docs.groovy-lang.org/latest/html/documentation/core-metaprogramming.html#metaprogramming
	 */
	@Keyword
	public static void pandemic() {
		WebUiBuiltInKeywords.metaClass.'static'.invokeMethod = { String name, args ->
			if (name in influencedKeywords) {
				TestObject to = (TestObject)args[0]
				String toString = args[0].toString().replaceFirst(/^TestObject - '(.*?)'$/, '$1')
				GlobalVariable.G_['lastWebElements'] = (GlobalVariable.G_.containsKey('testStep')) ? GlobalVariable.G_['testStep']['webElements'] : null
				def currentWebElements = HighlightElement.current(to)
				List inputParams = args.collect{ it }.withIndex().findResults{ it, id -> (id > 0) ? it : null }
				GlobalVariable.G_['testStep'] = ['keywordName' : name, 'testObject' : to, 'testObjectString' : toString, 'inputParams' : inputParams, 'webElements' : currentWebElements]
			}
			def result
			try {
				result = delegate.metaClass.getMetaMethod(name, args).invoke(delegate, args)
				if (name in influencedKeywords) {
					TestObject to = (TestObject)args[0]
					HighlightElement.success(to)
				}
			}
			catch(StepFailedException e) {
				if (name in influencedKeywords) {
					TestObject to = (TestObject)args[0]
					HighlightElement.failure(to)
					GlobalVariable.G_['exceptions']['Failure'] << e
				}
				throw e
			}
			catch(StepErrorException e) {
				if (name in influencedKeywords) {
					TestObject to = (TestObject)args[0]
					HighlightElement.failure(to)
					GlobalVariable.G_['exceptions']['Error'] << e
				}
				throw e
			}
			catch(Exception e) {
				if (name in influencedKeywords) {
					TestObject to = (TestObject)args[0]
					HighlightElement.failure(to)
					GlobalVariable.G_['exceptions']['Error'] << e
				}
				throw e
			}
			return result
		}
	}
}
1 Like