Diff
Modified: trunk/Tools/ChangeLog (91240 => 91241)
--- trunk/Tools/ChangeLog 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/ChangeLog 2011-07-19 07:38:05 UTC (rev 91241)
@@ -1,5 +1,35 @@
2011-07-19 Adam Barth <[email protected]>
+ garden-o-matic should have a "Triage Failures" button for iterating over failures
+ https://bugs.webkit.org/show_bug.cgi?id=64769
+
+ Reviewed by Eric Seidel.
+
+ This patch adds some global static state and refactors things a bit so
+ that we can iterate over all the failures in the details pane.
+
+ * Scripts/webkitpy/tool/servers/data/gardeningserver/base.js:
+ - Add a generic callback iterator to iterate through a series of
+ callbacks. We use this to iterate through the failures we want to
+ display the details of.
+ * Scripts/webkitpy/tool/servers/data/gardeningserver/index.html:
+ - Add a toolbar for the results summary and change the buttons to
+ be real buttons.
+ * Scripts/webkitpy/tool/servers/data/gardeningserver/main.css:
+ - Make the detail pane 75% of the window.
+ - Make the buttons pretty.
+ - Allow the failure type badges to apply to the details pane as well.
+ * Scripts/webkitpy/tool/servers/data/gardeningserver/main.js:
+ - Restructure how we bring up the details pane so we can iterate
+ through a bunch of failures.
+ - Remove the transition between results details because it's
+ annoying when you want to click through a bunch of failures.
+ * Scripts/webkitpy/tool/servers/data/gardeningserver/ui.js:
+ - Add some more structure to the title bar for the results details
+ so it's clearer which results we're currently displaying.
+
+2011-07-19 Adam Barth <[email protected]>
+
garden-o-matic should live update as conditions change on the buildbot
https://bugs.webkit.org/show_bug.cgi?id=64646
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base.js (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base.js 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base.js 2011-07-19 07:38:05 UTC (rev 91241)
@@ -99,16 +99,51 @@
base.RequestTracker = function(requestsInFlight, callback, args)
{
- this.m_requestsInFlight = requestsInFlight;
- this.m_callback = callback;
- this.m_args = args || [];
+ this._requestsInFlight = requestsInFlight;
+ this._callback = callback;
+ this._args = args || [];
};
base.RequestTracker.prototype.requestComplete = function()
{
- --this.m_requestsInFlight;
- if (!this.m_requestsInFlight)
- this.m_callback.apply(null, this.m_args);
+ --this._requestsInFlight;
+ if (!this._requestsInFlight)
+ this._callback.apply(null, this._args);
};
+base.CallbackIterator = function(callback, listOfArgumentArrays)
+{
+ this._callback = callback;
+ this._nextIndex = 0;
+ this._listOfArgumentArrays = listOfArgumentArrays;
+};
+
+base.CallbackIterator.prototype.hasNext = function()
+{
+ return this._nextIndex < this._listOfArgumentArrays.length;
+};
+
+base.CallbackIterator.prototype.hasPrevious = function()
+{
+ return this._nextIndex - 2 >= 0;
+};
+
+base.CallbackIterator.prototype.callNext = function()
+{
+ if (!this.hasNext())
+ return;
+ var args = this._listOfArgumentArrays[this._nextIndex];
+ this._nextIndex++;
+ this._callback.apply(null, args);
+};
+
+base.CallbackIterator.prototype.callPrevious = function()
+{
+ if (!this.hasPrevious())
+ return;
+ var args = this._listOfArgumentArrays[this._nextIndex - 2];
+ this._nextIndex--;
+ this._callback.apply(null, args);
+};
+
})();
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base_unittests.js (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base_unittests.js 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/base_unittests.js 2011-07-19 07:38:05 UTC (rev 91241)
@@ -72,6 +72,38 @@
tracker.requestComplete();
});
+test("CallbackIterator", 22, function() {
+ var expected = 0;
+ var iterator = new base.CallbackIterator(function(a, b) {
+ equals(a, 'ArgA' + expected);
+ equals(b, 'ArgB' + expected);
+ ++expected;
+ }, [
+ ['ArgA0', 'ArgB0'],
+ ['ArgA1', 'ArgB1'],
+ ['ArgA2', 'ArgB2'],
+ ]);
+ ok(iterator.hasNext())
+ ok(!iterator.hasPrevious())
+ iterator.callNext();
+ ok(iterator.hasNext())
+ ok(!iterator.hasPrevious())
+ iterator.callNext();
+ ok(iterator.hasNext())
+ ok(iterator.hasPrevious())
+ iterator.callNext();
+ ok(!iterator.hasNext())
+ ok(iterator.hasPrevious())
+ expected = 1;
+ iterator.callPrevious();
+ ok(iterator.hasNext())
+ ok(iterator.hasPrevious())
+ expected = 0;
+ iterator.callPrevious();
+ ok(iterator.hasNext())
+ ok(!iterator.hasPrevious())
+});
+
test("filterTree", 2, function() {
var tree = {
'path': {
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/index.html (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/index.html 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/index.html 2011-07-19 07:38:05 UTC (rev 91241)
@@ -12,14 +12,21 @@
<body>
<div class="butterbar"><span class="status">Loading...</span></div>
<div class="alert"><span class="status"></span></div>
-<div class="results"></div>
+<div class="results">
+ <div class="content"></div>
+ <div class="toolbar">
+ <div class="actions">
+ <button class="triage">Triage Failures</button>
+ </div>
+ </div>
+</div>
<div class="results-detail">
<div class="toolbar">
<div class="actions">
- <a class="rebaseline" href=""
- <a class="dismiss" href=""
+ <button class="rebaseline default">Rebaseline</button><button class="previous">◀</button><button class="next">▶</button><button class="dismiss">Close</button>
</div>
<div class="status"></div>
+ <div class="clear"></div>
</div>
<div class="content"></div>
</div>
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.css (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.css 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.css 2011-07-19 07:38:05 UTC (rev 91241)
@@ -7,7 +7,6 @@
a {
color: Black;
- font-weight: bold;
}
table {
@@ -19,7 +18,7 @@
td, th {
overflow: hidden;
vertical-align: top;
- border-right: 1px solid #CCC;
+ border-right: 1px solid #c6c6c6;
}
td:last-of-type, th:last-of-type {
@@ -27,13 +26,74 @@
}
th {
- border-bottom: 1px solid #CCC;
+ border-bottom: 1px solid #c6c6c6;
}
+button {
+ border-radius: 2px;
+ background-image: -webkit-linear-gradient(top,#f5f5f5,#f1f1f1);
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ border-radius: 2px;
+ color: #666;
+ cursor: pointer;
+ font-size: 11px;
+ font-weight: bold;
+ height: 29px;
+ line-height: 27px;
+ margin: 11px 6px;
+ min-width: 54px;
+ padding: 0 8px;
+ text-align: center
+}
+
+button.next {
+ margin-left: 0px;
+}
+
+button.previous {
+ margin-right: 0px;
+}
+
+button[disabled] {
+ visibility: hidden;
+}
+
+button:hover {
+ background-image: -webkit-linear-gradient(top,#f8f8f8,#f1f1f1);
+ -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1);
+ background-color: #f8f8f8;
+ background-image: linear-gradient(top,#f8f8f8,#f1f1f1);
+ border: 1px solid #c6c6c6;
+ box-shadow: 0 1px 1px rgba(0,0,0,0.1);
+ color: #333
+}
+
+button:focus {
+ border: 1px solid #4d90fe;
+ outline: none
+}
+
+button.default {
+ border: 1px solid #3079ED;
+ color: white;
+ background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed);
+}
+
+button.default:hover {
+ border: 1px solid #2f5bb7;
+ color: white;
+ background-color: #357ae8;
+ background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#357ae8));
+}
+
+.clear {
+ clear: both;
+}
+
/*** butterbar ***/
.butterbar {
- background-color: #f3f3f3;
+ background-color: #f5f5f5;
padding: 5px;
position: fixed;
top: 0px;
@@ -76,11 +136,16 @@
/*** results-summary ***/
+.results .toolbar {
+ text-align: center;
+ display: none;
+}
+
table.results-summary {
width: auto;
table-layout: auto;
margin: 5px auto;
- border: 1px solid #CCC;
+ border: 1px solid #c6c6c6;
}
.results-summary td, .results-summary th {
@@ -113,8 +178,9 @@
color: black;
}
-.results-summary .what a[draggable].IMAGE\+TEXT::after {
+.test-name.IMAGE\+TEXT::after {
content: 'IMAGE+TEXT';
+ font-weight: normal;
color: white;
background-color: Indigo;
font-size: 10px;
@@ -123,8 +189,9 @@
border-radius: 4px;
}
-.results-summary .what a[draggable].IMAGE::after {
+.test-name.IMAGE::after {
content: 'IMAGE';
+ font-weight: normal;
color: white;
background-color: MidnightBlue;
font-size: 10px;
@@ -133,8 +200,9 @@
border-radius: 4px;
}
-.results-summary .what a[draggable].TIMEOUT::after {
+.test-name.TIMEOUT::after {
content: 'TIMEOUT';
+ font-weight: normal;
color: white;
background-color: DarkGoldenRod;
font-size: 10px;
@@ -143,8 +211,9 @@
border-radius: 4px;
}
-.results-summary .what a[draggable].TEXT::after {
+.test-name.TEXT::after {
content: 'TEXT';
+ font-weight: normal;
color: white;
background-color: DodgerBlue;
font-size: 10px;
@@ -153,8 +222,9 @@
border-radius: 4px;
}
-.results-summary .what a[draggable].CRASH::after {
+.test-name.CRASH::after {
content: 'CRASH';
+ font-weight: normal;
color: white;
background-color: Tomato;
font-size: 10px;
@@ -174,34 +244,38 @@
.results-detail {
display: none;
position: fixed;
- background-color: #f3f3f3;
- top: 50%;
+ background-color: #f5f5f5;
+ top: 25%;
left: 0px;
right: 0px;
bottom: 0px;
}
.results-detail .toolbar {
- border-top: 1px solid #CCC;
- border-bottom: 1px solid #CCC;
+ border-top: 1px solid #c6c6c6;
+ border-bottom: 1px solid #c6c6c6;
}
.results-detail .toolbar .status {
- display: inline-block;
- padding: 5px;
+ font-size: 11px;
+ font-weight: bold;
+ height: 29px;
+ line-height: 27px;
+ margin: 11px 6px;
+ padding: 0 8px;
}
-.results-detail .toolbar .actions {
- float: right;
+.results-detail .toolbar .status .builder-name {
+ font-weight: normal;
+ padding-left: 20px;
}
-.results-detail .toolbar .actions a {
- display: inline-block;
- padding: 5px 5px 5px 0px;
+.results-detail .toolbar .status .selected {
+ font-style: italic;
}
-.results-detail .toolbar {
- position: relative;
+.results-detail .toolbar .actions {
+ float: right;
}
.results-detail .content {
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.js (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.js 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/main.js 2011-07-19 07:38:05 UTC (rev 91241)
@@ -1,6 +1,7 @@
(function() {
var g_updateTimerId = 0;
+var g_resultsDetailsIterator = null;
var kBuildFailedAlertType = 'build-failed';
@@ -12,7 +13,7 @@
function displayOnButterbar(message)
{
$('.butterbar .status').text(message);
- $('.butterbar').fadeIn();
+ $('.butterbar').fadeIn('fast');
}
function hideAlert()
@@ -47,16 +48,24 @@
$('#favicon').attr('href', faviconURL);
}
+function toggleButton(button, isEnabled)
+{
+ if (isEnabled)
+ button.removeAttr('disabled');
+ else
+ button.attr('disabled', true)
+}
+
function togglePartyTime(hasFailures)
{
if (!hasFailures) {
- $('.results').text('No failures. Party time!');
+ $('.results .content').text('No failures. Party time!');
var partyTime = $('<div class="partytime"><img src=""
- $('.results').append(partyTime);
+ $('.results .content').append(partyTime);
partyTime.fadeIn(1200).delay(7000).fadeOut();
return;
}
- $('.results').empty();
+ $('.results .content').empty();
}
function ensureResultsSummaryContainer()
@@ -65,7 +74,7 @@
if (container.length)
return container;
container = ui.regressionsContainer();
- $('.results').append(container);
+ $('.results .content').append(container);
return container;
}
@@ -117,6 +126,7 @@
var newTestSummaries = $();
var requestTracker = new base.RequestTracker(base.keys(unexpectedFailures).length, function() {
newTestSummaries.fadeIn();
+ $('.results .toolbar').fadeIn();
callback()
});
@@ -136,61 +146,106 @@
});
}
-function showResultsDetail()
+function showResultsDetail(testName, selectedBuilderName, failureTypeListByBuilder)
{
- var testSummary = $(this).parents('.test');
- var builderName = $(this).attr(config.kBuilderNameAttr);
- var testName = testSummary.attr(config.kTestNameAttr);
+ var builderNameList = base.keys(failureTypeListByBuilder);
+ var failureTypeList = failureTypeListByBuilder[selectedBuilderName];
+ var failureTypes = failureTypeList.join(' ');
- // FIXME: It's lame that we have two different representations of multiple failure types.
- var failureTypes = testSummary.attr(config.kFailureTypesAttr);
- var failureTypeList = failureTypes.split(' ');
+ var content = $('.results-detail .content');
- var content = $('.results-detail .content');
- if ($('.failure-details', content).attr(config.kBuilderNameAttr) == builderName &&
+ if ($('.failure-details', content).attr(config.kBuilderNameAttr) == selectedBuilderName &&
$('.failure-details', content).attr(config.kTestNameAttr) == testName &&
$('.failure-details', content).attr(config.kFailureTypesAttr) == failureTypes)
return;
displayOnButterbar('Loading...');
- results.fetchResultsURLs(builderName, testName, failureTypeList, function(resultsURLs) {
+ results.fetchResultsURLs(selectedBuilderName, testName, failureTypeList, function(resultsURLs) {
var status = $('.results-detail .toolbar .status');
- function updateResults()
- {
- status.text(testName + ' [' + builderName + ']');
- content.empty();
- content.append(ui.failureDetails(resultsURLs));
- $('.results-detail .actions .rebaseline').toggle(results.canRebaseline(failureTypeList));
- $('.failure-details', content).attr(config.kBuilderNameAttr, builderName);
- $('.failure-details', content).attr(config.kTestNameAttr, testName);
- $('.failure-details', content).attr(config.kFailureTypesAttr, failureTypes);
- }
+ status.empty();
+ content.empty();
- var children = content.children();
- if (children.length && $('.results-detail').is(":visible")) {
- // The results-detail pane is already open. Let's do a quick cross-fade.
- status.fadeOut('fast');
- children.fadeOut('fast', function() {
- updateResults();
- status.fadeIn('fast');
- content.children().hide().fadeIn('fast', dismissButterbar);
- });
- } else {
- updateResults();
+ status.append(ui.failureDetailsStatus(testName, selectedBuilderName, failureTypes, builderNameList));
+ content.append(ui.failureDetails(resultsURLs));
+
+ toggleButton($('.results-detail .actions .next'), g_resultsDetailsIterator.hasNext());
+ toggleButton($('.results-detail .actions .previous'), g_resultsDetailsIterator.hasPrevious());
+ toggleButton($('.results-detail .actions .rebaseline'), results.canRebaseline(failureTypeList));
+
+ $('.failure-details', content).attr(config.kBuilderNameAttr, selectedBuilderName);
+ $('.failure-details', content).attr(config.kTestNameAttr, testName);
+ $('.failure-details', content).attr(config.kFailureTypesAttr, failureTypes);
+
+ if (!$('.results-detail').is(":visible"))
$('.results-detail').fadeIn('fast', dismissButterbar);
- }
+ else
+ dismissButterbar();
});
}
function hideResultsDetail()
{
$('.results-detail').fadeOut('fast', function() {
+ $('.results-detail .status').empty();
$('.results-detail .content').empty();
+ // Strictly speaking, we don't need to clear g_resultsDetailsIterator,
+ // but doing so helps the garbage collector free memory.
+ g_resultsDetailsIterator = null;
});
}
+function nextResultsDetail()
+{
+ g_resultsDetailsIterator.callNext();
+}
+
+function previousResultsDetail()
+{
+ g_resultsDetailsIterator.callPrevious();
+}
+
+function resultsDetailArgumentsForBuilderElement(builderBlock)
+{
+ var selectedBuilderName = builderBlock.attr(config.kBuilderNameAttr);
+
+ var testSummary = builderBlock.parents('.test');
+ var testName = testSummary.attr(config.kTestNameAttr);
+
+ // FIXME: It's lame that we have two different representations of multiple failure types.
+ var failureTypes = testSummary.attr(config.kFailureTypesAttr);
+ var failureTypeList = failureTypes.split(' ');
+
+ var failureTypeListByBuilder = {}
+ $('.where .builder-name', testSummary).each(function() {
+ var builderName = $(this).attr(config.kBuilderNameAttr);
+ // FIXME: We should understand that tests can fail in different ways
+ // on different builders.
+ failureTypeListByBuilder[builderName] = failureTypeList;
+ });
+
+ return [testName, selectedBuilderName, failureTypeListByBuilder];
+}
+
+function prepareResultsDetailsIterator(query)
+{
+ var listOfResultsDetailArguments = [];
+
+ query.each(function() {
+ var resultsDetailArguments = resultsDetailArgumentsForBuilderElement($(this));
+ listOfResultsDetailArguments.push(resultsDetailArguments);
+ });
+
+ return new base.CallbackIterator(showResultsDetail, listOfResultsDetailArguments);
+}
+
+function triageFailures()
+{
+ g_resultsDetailsIterator = prepareResultsDetailsIterator($('.results .test .builder-name'));
+ g_resultsDetailsIterator.callNext();
+}
+
function rebaselineResults()
{
var failureDetails = $('.failure-details', $(this).parents('.results-detail'));
@@ -222,9 +277,11 @@
checkBuilderStatuses();
}
-$('.results-summary .where a').live('click', showResultsDetail);
+$('.results .toolbar .triage').live('click', triageFailures);
+$('.results-detail .actions .next').live('click', nextResultsDetail);
+$('.results-detail .actions .previous').live('click', previousResultsDetail);
+$('.results-detail .actions .rebaseline').live('click', rebaselineResults);
$('.results-detail .actions .dismiss').live('click', hideResultsDetail);
-$('.results-detail .actions .rebaseline').live('click', rebaselineResults);
$(document).ready(function() {
g_updateTimerId = window.setInterval(update, config.kUpdateFrequency);
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui.js (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui.js 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui.js 2011-07-19 07:38:05 UTC (rev 91241)
@@ -48,20 +48,20 @@
var unexpectedResults = results.collectUnexpectedResults(resultNodesByBuilder);
var block = $(
'<tr class="test">' +
- '<td class="what"><a draggable></a></td>' +
+ '<td class="what"><a class="test-name"></a></td>' +
'<td class="where"><ul></ul></td>' +
'<td class="when"></td>' +
'<td class="how-many"></td>' +
'</tr>');
- $('.what a', block).text(testName).attr('href', ui.urlForTest(testName)).attr('class', unexpectedResults.join(' '));
+ $('.test-name', block).text(testName).attr('href', ui.urlForTest(testName)).addClass(unexpectedResults.join(' '));
block.attr(config.kTestNameAttr, testName);
block.attr(config.kFailureTypesAttr, unexpectedResults);
var where = $('.where', block);
$.each(resultNodesByBuilder, function(builderName, resultNode) {
- var listElement = $('<li><a href=""
+ var listElement = $('<li class="builder-name"></li>');
+ listElement.attr(config.kBuilderNameAttr, builderName).text(displayNameForBuilder(builderName));
where.append(listElement);
- $('a', listElement).attr(config.kBuilderNameAttr, builderName).text(displayNameForBuilder(builderName));
});
return block;
@@ -79,7 +79,7 @@
displayNameForRevision(impliedFirstFailingRevision) :
displayNameForRevision(impliedFirstFailingRevision) + '-' + displayNameForRevision(oldestFailingRevision);
- var block = $('<div class="regression-range"><a></a></div>');
+ var block = $('<div class="regression-range"><a target="_blank"></a></div>');
$('a', block).attr('href', href).text(text)
return block;
};
@@ -107,6 +107,23 @@
return 'Seen ' + failureCount + ' times.';
};
+ui.failureDetailsStatus = function(testName, selectedBuilderName, failureTypes, builderNameList)
+{
+ var block = $('<span><span class="test-name"></span><span class="builder-list"></span></span>');
+ $('.test-name', block).addClass(failureTypes).text(testName);
+
+ var builderList = $('.builder-list', block);
+ $.each(builderNameList, function(index, builderName) {
+ var builder = $('<span class="builder-name"></span>')
+ builder.text(builderName);
+ if (builderName == selectedBuilderName)
+ builder.addClass('selected');
+ builderList.append(builder);
+ });
+
+ return block;
+};
+
ui.failureDetails = function(resultsURLs)
{
var block = $('<table class="failure-details"><tbody><tr></tr></tbody></table>');
Modified: trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui_unittests.js (91240 => 91241)
--- trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui_unittests.js 2011-07-19 07:37:01 UTC (rev 91240)
+++ trunk/Tools/Scripts/webkitpy/tool/servers/data/gardeningserver/ui_unittests.js 2011-07-19 07:38:05 UTC (rev 91241)
@@ -37,11 +37,11 @@
var summaryWithMultipleRevisions = ui.summarizeRegressionRange(90424, 90426);
summaryWithMultipleRevisions.wrap('<wrapper></wrapper>');
- equal(summaryWithMultipleRevisions.parent().html(), '<div class="regression-range"><a href=""
+ equal(summaryWithMultipleRevisions.parent().html(), '<div class="regression-range"><a target="_blank" href=""
var summaryWithOneRevision = ui.summarizeRegressionRange(90425, 90426);
summaryWithOneRevision.wrap('<wrapper></wrapper>');
- equal(summaryWithOneRevision.parent().html(), '<div class="regression-range"><a href=""
+ equal(summaryWithOneRevision.parent().html(), '<div class="regression-range"><a target="_blank" href=""
});
test("failureCount", 4, function() {
@@ -61,6 +61,19 @@
'</ul></div>');
});
+test("failureDetailsStatus", 1, function() {
+ var status = ui.failureDetailsStatus('userscripts/another-test.html', 'Mock Builder', 'TEXT', ['Mock Builder', 'Another Builder']);
+ status.wrap('<wrapper></wrapper>');
+ equal(status.parent().html(),
+ '<span>' +
+ '<span class="test-name TEXT">userscripts/another-test.html</span>' +
+ '<span class="builder-list">' +
+ '<span class="builder-name selected">Mock Builder</span>' +
+ '<span class="builder-name">Another Builder</span>' +
+ '</span>' +
+ '</span>');
+});
+
test("failureDetails", 1, function() {
var testResults = ui.failureDetails([
'http://example.com/layout-test-results/foo-bar-diff.txt',