Instantly share code, notes, and snippets.

@jrfnl /_readme.md
Last active Oct 8, 2018

Embed
What would you like to do?
PHPUnit xslt

###PHPUnit logfile XLST template

This fileset provides an easy way to view the PHPUnit XML (JUnit) logfiles in a human readable manner using a web browser.

Use this either in combination with the accompanying html file or add the following tag straight after the xml opening tag of the logfile: <?xml-stylesheet type="text/xsl" href="phpunit.xslt"?>

The thresholds used for the colour-coding and whether or not to show detail for successfull tests can be changed by adjusting the variables at the top of the xslt file.

Copyright ©2014 Juliette Reinders Folmer (Twitter: @jrf_nl / GitHub: @jrfnl).

License: DWTFYW

Updates will be published via: https://gist.github.com/jrfnl/3c28ea6d9b07fd48656d

Loosely inspired by: https://www.ruby-forum.com/topic/120869

NB: This was quickly thrown together. There are probably better ways to do bits of it. Feel free to suggest them via the gist comment form ;-)

screenshot

(larger screenshot)

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
PHPUnit logfile XLST template
This fileset provides an easy way to view the PHPUnit XML (JUnit) logfiles in a human readable manner using a web browser.
Use this either in combination with the accompanying html file or add the following tag straight
after the xml opening tag of the logfile:
<?xml-stylesheet type="text/xsl" href="phpunit.xslt"?>
The thresholds used for the colour-coding can be changed by adjusting the variables at the top of this file.
Be careful when changing the values: the double quoting is intentional and needed. Don't remove.
Copyright ©2014 Juliette Reinders Folmer (Twitter: @jrf_nl / GitHub: @jrfnl)
License: DWTFYW
Updates will be published via: https://gist.github.com/jrfnl/3c28ea6d9b07fd48656d
Loosely inspired by: https://www.ruby-forum.com/topic/120869
NB: This was quickly thrown together. There are probably better ways to do bits of it.
Feel free to suggest them via the gist comment form ;-)
-->
<!--
The percentage of tests which need to have failed for the failures cell colour to go from orange to red.
Default value: 0.4 (=40%)
-->
<xsl:variable name="fail_bad" select="'0.4'" />
<!--
The thresholds used for the time colour coding in seconds.
-->
<xsl:variable name="very_bad_time" select="'10'" />
<xsl:variable name="bad_time" select="'3'" />
<xsl:variable name="not_so_good_time" select="'1'" />
<xsl:variable name="good_time" select="'0.4'" />
<!--
Whether to show the details of passed tests. Set to 1 to show.
Default value: 0 (no)
-->
<xsl:variable name="show_success_detail" select="'0'" />
<xsl:template match="/">
<html>
<head>
<style type="text/css">
* {
font-family: tahoma, verdana, sans-serif;
font-size: 96%;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h3, h4 {
padding: 1em 0 0.5em;
}
table {
width: 100%;
border-collapse: collapse;
}
table#high-level {
width: auto;
}
table tr {
vertical-align: top;
}
table td, table th {
padding: 0.2em 1em 0.3em 1em;
}
table th {
text-align: left;
}
table tr.top {
background-color: #dddddd;
border-bottom: 2px solid #000000;
}
table tr.test-file {
margin-top: 0.2em;
font-weight: bold;
background-color: #eeeeee;
border-top: 1px solid #666666;
}
table tr.test-file th {
padding-top: 0.5em;
}
table tr.single-test th {
padding-left: 3em;
}
table#summary td {
border-left: 1px solid #cccccc;
border-right: 1px solid #cccccc;
}
table td.nr {
text-align: center;
}
table td.time {
text-align: right;
}
.pass {
color: #999999;
background-color: #C1FFC1;
}
.failed {
background-color: #FFDAB9;
}
.errored {
background-color: #FFB8BA;
}
.fail, .fail-lot {
color: #FFFFEE;
background-color: #C51F1F;
font-weight: bold;
}
.fail-some {
color: #FFFFEE;
background-color: #FFB90F;
font-weight: bold;
}
.very-bad-time {
background-color: #FA8072;
font-weight: bold;
}
.bad-time {
background-color: #FFA54F;
}
.not-so-good-time {
background-color: #FFEC8B;
}
.sort-of-ok {
background-color: #BFEFFF;
}
.good-time {
background-color: #C1FFC1;
}
.no-tests {
text-decoration: line-through;
text-decoration-style: double;
}
.error-detail-type, .fail-detail-type {
padding-left: 2em;
font-weight: bold;
}
.error-detail-detail, .fail-detail-detail {
padding-left: 4em;
white-space: pre-wrap;
padding-bottom: 1em;
}
.backlink {
text-align: right;
font-size: 80%;
}
.backlink span {
font-size: 140%;
}
</style>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="/testsuites/testsuite" mode="high-level">
<h2>Test Totals: <xsl:value-of select="@name"/></h2>
<table id="high-level">
<tr>
<td><b>Number of Tests:</b></td>
<td><xsl:value-of select="@tests"/></td>
</tr>
<tr>
<td><b>Number of Assertions:</b></td>
<td><xsl:value-of select="@assertions"/></td>
</tr>
<tr>
<td><b>Number of Failures:</b></td>
<td><xsl:value-of select="@failures"/></td>
</tr>
<tr>
<td><b>Number of Errors:</b></td>
<td><xsl:value-of select="@errors"/></td>
</tr>
<tr>
<td><b>Execution Time:</b></td>
<td><xsl:value-of select="@time"/></td>
</tr>
</table>
</xsl:template>
<xsl:template match="testsuites">
<div>
<xsl:apply-templates select="/testsuites/testsuite" mode="high-level"/>
</div>
<h3>Summary</h3>
<table id="summary">
<tr class="top">
<th>Name</th>
<th>Tests</th>
<th>Assertions</th>
<th>Failures</th>
<th>Errors</th>
<th>Execution Time</th>
</tr>
<xsl:apply-templates select="//testsuite"/>
</table>
<xsl:apply-templates select="//testsuite[count(testsuite) = 0]" mode="details"/>
</xsl:template>
<xsl:template match="//testsuite">
<xsl:variable name="hasfailures">
<xsl:choose>
<xsl:when test="(@failures div @tests ) > $fail_bad"> fail-lot</xsl:when>
<xsl:when test="@failures &gt; 0"> fail-some</xsl:when>
<xsl:when test="@tests &gt; 0 and @failures = 0 and @errors = 0"> pass</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="haserrors">
<xsl:choose>
<xsl:when test="@errors &gt; 0"> fail</xsl:when>
<xsl:when test="@tests &gt; 0 and @failures = 0 and @errors = 0"> pass</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="passes">
<xsl:choose>
<xsl:when test="@tests &gt; 0 and @failures = 0 and @errors = 0"> pass</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="isslow">
<xsl:choose>
<xsl:when test="@time &gt; $very_bad_time"> very-bad-time</xsl:when>
<xsl:when test="@time &gt; $bad_time"> bad-time</xsl:when>
<xsl:when test="@time &gt; $not_so_good_time"> not-so-good-time</xsl:when>
<xsl:when test="@tests &gt; 0 and @time &lt; $good_time"> good-time</xsl:when>
<xsl:when test="@tests = 0"></xsl:when>
<xsl:otherwise> sort-of-ok</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="testname" select="@name" />
<xsl:choose>
<xsl:when test="count(testsuite) = 0">
<tr class="single-test">
<xsl:choose>
<xsl:when test="count(testcase) &gt; 0">
<th><a href="#{$testname}"><xsl:value-of select="@name"/></a></th>
</xsl:when>
<xsl:otherwise>
<th><xsl:value-of select="@name"/></th>
</xsl:otherwise>
</xsl:choose>
<td class="nr{$passes}"><xsl:value-of select="@tests"/></td>
<td class="nr{$passes}"><xsl:value-of select="@assertions"/></td>
<td class="nr{$hasfailures}"><xsl:value-of select="@failures"/></td>
<td class="nr{$haserrors}"><xsl:value-of select="@errors"/></td>
<td class="time{$isslow}"><xsl:value-of select="@time"/></td>
</tr>
</xsl:when>
<xsl:when test="@name != ''">
<tr class="test-file">
<th><xsl:value-of select="@name"/></th>
<td class="nr{$passes}"><xsl:value-of select="@tests"/></td>
<td class="nr{$passes}"><xsl:value-of select="@assertions"/></td>
<td class="nr{$hasfailures}"><xsl:value-of select="@failures"/></td>
<td class="nr{$haserrors}"><xsl:value-of select="@errors"/></td>
<td class="time{$isslow}"><xsl:value-of select="@time"/></td>
</tr>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="//testsuite[count(testsuite) = 0]" mode="details">
<xsl:variable name="testname" select="@name" />
<xsl:choose>
<xsl:when test="count(testcase) &gt; 0 and ( $show_success_detail = 1 or ( $show_success_detail = 0 and ( @failures &gt; 0 or @errors &gt; 0 ) ) )">
<h4 id="{$testname}"><xsl:value-of select="@name"/></h4>
<table>
<tr class="top">
<th>Test name</th>
<th>Assertions</th>
<th>Time</th>
</tr>
<xsl:apply-templates select="testcase"/>
</table>
<p class="backlink"><a href="#summary"><span>&#8613;</span> Back to summary</a></p>
</xsl:when>
<xsl:when test="count(testcase) = 0">
<h4 id="{$testname}"><span class="no-tests"><xsl:value-of select="@name"/></span> (no tests found)</h4>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="testcase">
<xsl:variable name="isslow">
<xsl:choose>
<xsl:when test="@time &gt; $very_bad_time"> very-bad-time</xsl:when>
<xsl:when test="@time &gt; $bad_time"> bad-time</xsl:when>
<xsl:when test="@time &gt; $not_so_good_time"> not-so-good-time</xsl:when>
<xsl:when test="@tests &gt; 0 and @time &lt; $good_time"> good-time</xsl:when>
<xsl:when test="@tests = 0"></xsl:when>
<xsl:otherwise> sort-of-ok</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="class">
<xsl:choose>
<xsl:when test="count(failure) &gt; 0">failed</xsl:when>
<xsl:when test="count(error) &gt; 0">errored</xsl:when>
<xsl:otherwise>pass</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr class="{$class}">
<th><xsl:value-of select="@name"/></th>
<td><xsl:value-of select="@assertions"/></td>
<td class="time{$isslow}"><xsl:value-of select="@time"/></td>
</tr>
<xsl:apply-templates select="error"/>
<xsl:apply-templates select="failure"/>
</xsl:template>
<xsl:template match="error">
<tr>
<td class="error-detail-type" colspan="3"><xsl:value-of select="@type"/></td>
</tr>
<tr>
<td class="error-detail-detail" colspan="3"><xsl:value-of select="."/></td>
</tr>
</xsl:template>
<xsl:template match="failure">
<tr>
<td class="fail-detail-type" colspan="3"><xsl:value-of select="@type"/></td>
</tr>
<tr>
<td class="fail-detail-detail" colspan="3"><xsl:value-of select="."/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
<html>
<head>
<!--
Source: http://www.w3schools.com/xsl/xsl_client.asp
Auto-generate from xslt and xml for PHPUnit logfile.
@see https://gist.github.com/jrfnl/3c28ea6d9b07fd48656d
Make sure you point the file to your .xml logfile in the displayResult() function!
-->
<title>PHPUnit test results</title>
<script type="text/javascript">
function loadXMLDoc( filename ) {
if (window.ActiveXObject) {
xhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
else {
xhttp = new XMLHttpRequest();
}
xhttp.open("GET", filename, false);
try {xhttp.responseType = "msxml-document"} catch(err) {} // Helping IE11
xhttp.send("");
return xhttp.responseXML;
}
function displayResult() {
/**
* @todo Change the file name here to point to your logfile location
*/
xml = loadXMLDoc("phpunit-test-log.xml");
xsl = loadXMLDoc("phpunit.xslt");
// code for IE
if (window.ActiveXObject || xhttp.responseType == "msxml-document") {
ex = xml.transformNode(xsl);
document.getElementById("example").innerHTML = ex;
}
// code for Chrome, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument) {
xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
resultDocument = xsltProcessor.transformToFragment(xml, document);
document.getElementById("example").appendChild(resultDocument);
}
}
</script>
</head>
<body onload="displayResult()">
<div id="example" />
</div>
</body>
</html>
@alexscott64

This comment has been minimized.

alexscott64 commented Jun 23, 2018

If some tests use a data provider (resulting in nested test suites) and some tests do not use a data provider, the tests that do not use a data provider end up getting put under the wrong header. Is there any way to fix this?

You can see this issue with the following xml excerpt:

<testsuite name="ClassOne" file="ClassOneTest.php" tests="2" assertions="2" errors="0" failures="0" skipped="0" time="0.006163">
    <testsuite name="ClassOne::myFirstTest tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.028940">
        <testcase name="test_my_first_test with data set &quot;Only data" class="ClassOneTest" classname="ClassOne" file="ClassOneTest.php" line="5" assertions="1" time="0.012244" />
    </testsuite>
    <testcase name="mySecondTest" class="ClassOneTest" classname="ClassOneTest" file="ClassOneTest.php" assertions="2" time="0.003331" />
</testsuite>
<testsuite name="ClassTwo" file="ClassTwoTest.php" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.006163">
    <testcase name="myThirdTest" class="ClassTwoTest" classname="ClassTwoTest" file="ClassTwoTest.php" line="10" assertions="2" time="0.003331" />
</testsuite>

This results in the the following output:


ClassOne

  • ClassOneTest::myFirstTest
  • ClassTwoTest_myThirdTest

But I was hoping it would look like this:


ClassOne

  • ClassOneTest::myFirstTest
  • mySecondTest

ClassTwo

  • myThirdTest

How can I get the second output, with correct headings?

@EdmundoDelGusto

This comment has been minimized.

EdmundoDelGusto commented Jul 25, 2018

@alexscott64
This may be not an elegant solution. I never worked with XSLT until know.

But I had the same Problem as you and here is how i fixed it:
Line 218 is:
<xsl:apply-templates select="//testsuite[count(testsuite) = 0]" mode="details"/>

And above that line paste the following Line:

<xsl:apply-templates select="//testsuite[testsuite[count(testsuite) = 0]" mode="details"/>

So you got:

<xsl:apply-templates select="//testsuite[testsuite[count(testsuite) = 0]]" mode="details"/>
<xsl:apply-templates select="//testsuite[count(testsuite) = 0]" mode="details"/>

After that copy paste this block:

<xsl:template match="//testsuite[testsuite[count(testsuite) = 0]]" mode="details">
	<xsl:variable name="testname" select="@name" />

	<xsl:choose>
		<xsl:when test="count(testcase) &gt; 0 and ( $show_success_detail = 1 or ( $show_success_detail = 0 and ( @failures &gt; 0 or @errors &gt; 0 ) ) )">

	<h4 id="{$testname}"><xsl:value-of select="@name"/></h4>
	<table>
		<tr class="top">
			<th>Test name</th>
			<th>Assertions</th>
			<th>Time</th>
		</tr>
	    <xsl:apply-templates select="testcase"/>
	</table>
	<p class="backlink"><a href="#summary"><span>&#8613;</span> Back to summary</a></p>

		</xsl:when>
		<xsl:otherwise></xsl:otherwise>
	</xsl:choose>
</xsl:template>

Also change line 258:
<xsl:when test="count(testsuite) = 0">
to
<xsl:when test="(count(testsuite[testsuite]) = 0 and count(testcase) != 0)">

works for me..

Now it looks like this (with show_success_detail=1)
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment