Diff
Added: trunk/Websites/test-results/ChangeLog (0 => 157659)
--- trunk/Websites/test-results/ChangeLog (rev 0)
+++ trunk/Websites/test-results/ChangeLog 2013-10-19 01:47:40 UTC (rev 157659)
@@ -0,0 +1,31 @@
+2013-10-18 Ryosuke Niwa <[email protected]>
+
+ New flakiness dashboard should support showing the failing tests per builder
+ https://bugs.webkit.org/show_bug.cgi?id=123011
+
+ Reviewed by Timothy Hatcher.
+
+ Added the feature. Also did some refactoring to add this feature.
+
+ * ChangeLog: Added.
+ * api/failing-tests.php: Added.
+ * api/manifest.php: Removed the code to make maps by id. The work is now done in index.html.
+ * api/results.php:
+ * common.css: Added. Extracted from index.html.
+ * include/test-results.php: Extracted parse_revisions_array and format_result_rows from results.php.
+ * index.html:
+ * main.css: Added.
+ (TestResultsView.setAvailableTests): Added.
+ (TestResultsView.showTooltip): Fixed the code to compute x and y coordinates of the tooltip to take
+ scrolled positions into account.
+ (TestResultsView._createTestResultRow): Extracted from _populateTestPane.
+ (TestResultsView.fetchTest): Added the code to show "Loading..." in the pane while loading the JSON.
+ (TestResultsView.fetchTests): Respect the doNotUpdateHash flag.
+ (TestResultsView._populateBuilderPane): Added.
+ (TestResultsView.fetchFailingTestsForBuilder): Added.
+ (TestResultsView.updateLocationHash): Serialize builder & builderDays.
+ (TestResultsView.locationHashChanged): Don't delete existing test panes since that's now done in
+ loadTestsFromLocationHash.
+ (TestResultsView.loadTestsFromLocationHash): Take care of both 'tests' and 'builder' components.
+ (fetchManifest): Setup the UI to select a builder.
+
Added: trunk/Websites/test-results/api/failing-tests.php (0 => 157659)
--- trunk/Websites/test-results/api/failing-tests.php (rev 0)
+++ trunk/Websites/test-results/api/failing-tests.php 2013-10-19 01:47:40 UTC (rev 157659)
@@ -0,0 +1,33 @@
+<?php
+
+require_once('../include/json-shared.php');
+require_once('../include/test-results.php');
+
+$db = connect();
+
+require_existence_of($_GET, array('builder' => '/^[A-Za-z0-9 \(\)\-_]+$/'));
+$builder_name = $_GET['builder'];
+$number_of_days = array_get($_GET, 'days');
+if ($number_of_days) {
+ require_format('number_of_days', $number_of_days, '/^[0-9]+$/');
+ $number_of_days = intval($number_of_days);
+} else
+ $number_of_days = 3;
+
+$builder_row = $db->select_first_row('builders', NULL, array('name' => $builder_name));
+if (!$builder_row)
+ exit_with_error('BuilderNotFound');
+$builder_id = $builder_row['id'];
+
+$result_rows = $db->query_and_fetch_all(
+'SELECT results.*, builds.*, array_agg((build_revisions.repository, build_revisions.value, build_revisions.time)) AS revisions
+ FROM results, builds, build_revisions
+ WHERE build_revisions.build = builds.id AND results.build = builds.id AND builds.builder = $1
+ AND results.actual != $2 AND builds.start_time > now() - interval \'' . $number_of_days . ' days\'
+ GROUP BY results.id, builds.id', array($builder_id, 'PASS'));
+if (!$result_rows)
+ exit_with_error('ResultsNotFound');
+
+exit_with_success(format_result_rows($result_rows));
+
+?>
Modified: trunk/Websites/test-results/api/manifest.php (157658 => 157659)
--- trunk/Websites/test-results/api/manifest.php 2013-10-19 00:51:07 UTC (rev 157658)
+++ trunk/Websites/test-results/api/manifest.php 2013-10-19 01:47:40 UTC (rev 157659)
@@ -3,25 +3,10 @@
require_once('../include/json-shared.php');
$db = connect();
-$tests = $db->fetch_table('tests');
-function fetch_table_and_create_map_by_id($table_name) {
- global $db;
+exit_with_success(array('tests' => $db->fetch_table('tests'),
+ 'builders' => $db->fetch_table('builders'),
+ 'slaves' => $db->fetch_table('slaves'),
+ 'repositories' => $db->fetch_table('repositories')));
- $rows = $db->fetch_table($table_name);
- if (!$rows)
- return array();
-
- $results = array();
- foreach ($rows as $row) {
- $results[$row['id']] = $row;
- }
- return $results;
-}
-
-exit_with_success(array('tests' => $tests,
- 'builders' => fetch_table_and_create_map_by_id('builders'),
- 'slaves' => fetch_table_and_create_map_by_id('slaves'),
- 'repositories' => fetch_table_and_create_map_by_id('repositories')));
-
?>
Modified: trunk/Websites/test-results/api/results.php (157658 => 157659)
--- trunk/Websites/test-results/api/results.php 2013-10-19 00:51:07 UTC (rev 157658)
+++ trunk/Websites/test-results/api/results.php 2013-10-19 01:47:40 UTC (rev 157659)
@@ -1,6 +1,7 @@
<?php
require_once('../include/json-shared.php');
+require_once('../include/test-results.php');
$db = connect();
@@ -13,36 +14,11 @@
$result_rows = $db->query_and_fetch_all(
'SELECT results.*, builds.*, array_agg((build_revisions.repository, build_revisions.value, build_revisions.time)) AS revisions
FROM results, builds, build_revisions
- WHERE build_revisions.build = builds.id AND results.test = $1 AND results.build = builds.id
+ WHERE build_revisions.build = builds.id AND results.build = builds.id AND results.test = $1
GROUP BY results.id, builds.id', array($test['id']));
if (!$result_rows)
exit_with_error('ResultsNotFound');
-date_default_timezone_set('UTC');
-function parse_revisions_array($postgres_array) {
- // e.g. {"(WebKit,131456,\"2012-10-16 14:53:00\")","(Safari,162004,)"}
- $outer_array = json_decode('[' . trim($postgres_array, '{}') . ']');
- $revisions = array();
- foreach ($outer_array as $item) {
- $name_and_revision = explode(',', trim($item, '()'));
- $time = strtotime(trim($name_and_revision[2], '"')) * 1000;
- $revisions[trim($name_and_revision[0], '"')] = array(trim($name_and_revision[1], '"'), $time);
- }
- return $revisions;
-}
+exit_with_success(format_result_rows($result_rows));
-$builders = array();
-foreach ($result_rows as $result) {
- array_push(array_ensure_item_has_array($builders, $result['builder']),
- array('buildTime' => strtotime($result['start_time']) * 1000,
- 'revisions' => parse_revisions_array($result['revisions']),
- 'builder' => $result['builder'],
- 'slave' => $result['slave'],
- 'buildNumber' => $result['number'],
- 'actual' => $result['actual'],
- 'expected' => $result['expected']));
-}
-
-exit_with_success(array('builders' => $builders));
-
?>
Added: trunk/Websites/test-results/common.css (0 => 157659)
--- trunk/Websites/test-results/common.css (rev 0)
+++ trunk/Websites/test-results/common.css 2013-10-19 01:47:40 UTC (rev 157659)
@@ -0,0 +1,65 @@
+
+html, body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ background-repeat: repeat-x;
+ font-family: sans-serif;
+ padding: 10px;
+}
+
+#title {
+ background-image: linear-gradient(bottom, rgb(240,240,240) 31%, rgb(255,255,255) 90%);
+ background-image: -o-linear-gradient(bottom, rgb(240,240,240) 31%, rgb(255,255,255) 90%);
+ background-image: -moz-linear-gradient(bottom, rgb(240,240,240) 31%, rgb(255,255,255) 90%);
+ background-image: -webkit-linear-gradient(bottom, rgb(240,240,240) 31%, rgb(255,255,255) 90%);
+ background-image: -ms-linear-gradient(bottom, rgb(240,240,240) 31%, rgb(255,255,255) 90%);
+ -moz-box-shadow: 1px 1px 3px 1px #ccc;
+ -webkit-box-shadow: 1px 1px 3px 1px #ccc;
+ box-shadow: 1px 1px 3px 1px #ccc;
+ padding: 5px 10px;
+ margin: 0 0 20px 0;
+ border-radius: 5px;
+ position: relative;
+}
+
+#title h1 {
+ font-weight: normal;
+ text-shadow: #bbb 1px 1px 2px;
+ margin: 0;
+ padding: 0;
+ font-size: 2em;
+}
+#title li, #title ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#title li {
+ display: inline;
+}
+
+#title li:after {
+ content: ' | ';
+}
+
+#title li:last-child:after {
+ content: '';
+}
+
+#title ul {
+ position: absolute;
+ vertical-align: middle;
+ top: 5px;
+ right: 20px;
+ padding-top: 0.6em;
+}
+
+a {
+ text-decoration: none;
+ color: #000;
+ text-shadow: #bbb 1px 1px 2px;
+}
Modified: trunk/Websites/test-results/include/test-results.php (157658 => 157659)
--- trunk/Websites/test-results/include/test-results.php 2013-10-19 00:51:07 UTC (rev 157658)
+++ trunk/Websites/test-results/include/test-results.php 2013-10-19 01:47:40 UTC (rev 157659)
@@ -66,4 +66,31 @@
'expected' => $tests['expected'], 'actual' => $tests['actual']));
}
+date_default_timezone_set('UTC');
+function parse_revisions_array($postgres_array) {
+ // e.g. {"(WebKit,131456,\"2012-10-16 14:53:00\")","(Safari,162004,)"}
+ $outer_array = json_decode('[' . trim($postgres_array, '{}') . ']');
+ $revisions = array();
+ foreach ($outer_array as $item) {
+ $name_and_revision = explode(',', trim($item, '()'));
+ $time = strtotime(trim($name_and_revision[2], '"')) * 1000;
+ $revisions[trim($name_and_revision[0], '"')] = array(trim($name_and_revision[1], '"'), $time);
+ }
+ return $revisions;
+}
+
+function format_result_rows($result_rows) {
+ $builders = array();
+ foreach ($result_rows as $result) {
+ array_push(array_ensure_item_has_array(array_ensure_item_has_array($builders, $result['builder']), $result['test']),
+ array('buildTime' => strtotime($result['start_time']) * 1000,
+ 'revisions' => parse_revisions_array($result['revisions']),
+ 'slave' => $result['slave'],
+ 'buildNumber' => $result['number'],
+ 'actual' => $result['actual'],
+ 'expected' => $result['expected']));
+ }
+ return array('builders' => $builders);
+}
+
?>
Modified: trunk/Websites/test-results/index.html (157658 => 157659)
--- trunk/Websites/test-results/index.html 2013-10-19 00:51:07 UTC (rev 157658)
+++ trunk/Websites/test-results/index.html 2013-10-19 01:47:40 UTC (rev 157659)
@@ -2,7 +2,8 @@
<html>
<head>
<title>WebKit Test Results</title>
-<link rel="stylesheet" href=""
+<link rel="stylesheet" href=""
+<link rel="stylesheet" href=""
<script src=""
<script src=""
<script src=""
@@ -16,178 +17,22 @@
</ul>
</header>
-<form id="navigationBar" _onsubmit_="TestResultsView.fetchTest(this['testName'].value);
+<form _onsubmit_="TestResultsView.fetchTest(this['testName'].value);
TestResultsView.updateLocationHash();
return false;">
<input id="testName" type="text" size="150" _onpaste_="pasteHelper(this, event)"
placeholder="Type in a test name, or copy and paste test names on results.html or NRWT stdout (including junks)"></form>
+<div id="testView"></div>
-<div id="container"></div>
+<form>
+<label>Show failing tests for</label>
+<select id="builderListView"><option value="">Select builder</option></select>
+<select id="builderDaysView"><option>5</option><option>15</option><option>30</option></select>
+</form>
+<div id="builderFailingTestsView"></div>
+
<div id="tooltipContainer"></div>
-<style>
-
-#testName {
- width: 99%;
- font-size: 1em;
- outline: none;
- border: 1px solid #ccc;
- border-radius: 5px;
- padding: 5px;
-}
-
-.testResults {
- border: 1px solid #ccc;
- border-radius: 5px;
- padding: 5px;
- margin: 10px 0px;
- position: relative;
-}
-
-.closeButton {
- position: absolute;
- right: 5px;
- top: 5px;
- width: 1em;
- height: 1em;
- stroke: #999;
-}
-
-.resultsTable {
- font-size: small;
- border-collapse: collapse;
- border: 2px solid #fff;
- padding: 0;
- margin: 0;
-}
-
-.resultsTable caption {
- font-size: large;
- font-weight: normal;
- text-align: left;
- margin-bottom: 0.3em;
- white-space: pre;
-}
-
-.resultsTable td,
-.resultsTable th {
- border: 2px solid #fff;
- padding: 0;
- margin: 0;
-}
-
-.resultsTable th,
-.resultsTable .passingRate {
- font-weight: normal;
- padding-right: 10px;
-}
-
-.resultsTable th {
- width: 15em;
-}
-
-.resultsTable .passingRate {
- width: 3em;
-}
-
-.resultsTable .resultCell {
- display: inline-block;
- padding: 0.2em 0.2em;
-}
-
-.resultsTable a {
- display: block;
- width: 1em;
- height: 1.5em;
- border-radius: 3px;
-}
-
-.resultsTable .PASS a {
- background-color: #0c3;
-}
-
-.resultsTable .TEXT a {
- background-color: #c33;
-}
-
-.resultsTable .IMAGE a {
- background-color: #3cf;
-}
-
-.resultsTable .TEXT.PASS a {
- background-color: #cf3;
-}
-
-.resultsTable .CRASH a {
- background-color: #f00;
-}
-
-.candidateWindow {
- z-index: 999;
- position: absolute;
- background: white;
- color: black;
- border: 1px solid #ccc;
- border-radius: 5px;
- margin: 5px 0 0 0;
- padding: 5px;
- font-size: 1em;
- list-style: none;
-}
-
-.candidateWindow em {
- background-color: #ccc;
- font-style: normal;
-}
-
-.candidateWindow .selected {
- background-color: #0cf;
- color: white;
-}
-
-#tooltipContainer {
- position: absolute;
-}
-
-.tooltip {
- position: relative;
- border-radius: 5px;
- padding: 5px;
- opacity: 0.9;
- background: #333;
- color: #eee;
- font-size: small;
- line-height: 130%;
-}
-
-.tooltip:after {
- position: absolute;
- width: 0;
- height: 0;
- left: 50%;
- margin-left: -9px;
- bottom: -19px;
- content: "";
- display: block;
- border-style: solid;
- border-width: 10px;
- border-color: #333 transparent transparent transparent;
-}
-
-.tooltip ul,
-.tooltip li {
- padding: 0;
- margin: 0;
- list-style: none;
-}
-
-.tooltip a {
- color: white;
- text-shadow: none;
- text-decoration: underline;
-}
-
-</style>
<script>
var TestResultsView = new (function () {
@@ -205,12 +50,17 @@
this._tooltipContainer = tooltipContainer;
this._tests = [];
+ this._currentBuilder = null;
this._oldHash = null;
this._builders = [];
this._slaves = [];
this._repositories = [];
});
+TestResultsView.setAvailableTests = function (availableTests) {
+ this._availableTests = availableTests;
+}
+
TestResultsView.setBuilders = function (builders) {
this._builders = builders;
}
@@ -231,9 +81,15 @@
tooltipContainer.removeChild(tooltipContainer.firstChild);
tooltipContainer.appendChild(contentElement);
- var rect = anchor.getBoundingClientRect();
- tooltipContainer.style.left = (rect.left - contentElement.offsetWidth / 2 + anchor.offsetWidth / 2) + 'px';
- tooltipContainer.style.top = (rect.top - contentElement.clientHeight - 5) + 'px';
+ var position = {x: 0, y: 0};
+ var currentNode = anchor;
+ while (currentNode) {
+ position.x += currentNode.offsetLeft;
+ position.y += currentNode.offsetTop;
+ currentNode = currentNode.offsetParent;
+ }
+ tooltipContainer.style.left = (position.x - contentElement.offsetWidth / 2 + anchor.offsetWidth / 2) + 'px';
+ tooltipContainer.style.top = (position.y - contentElement.clientHeight - 5) + 'px';
}
TestResultsView._urlFromBuilder = function (urlType, master, builder, revision, build) {
@@ -282,30 +138,38 @@
return cell;
}
-TestResultsView._populatePane = function(testName, results, section) {
+TestResultsView._populateTestPane = function(testName, results, section) {
var table = element('table', {'class': 'resultsTable'}, [element('caption', [testName])]);
var resultsByBuilder = results['builders'];
for (var builderId in resultsByBuilder) {
- var results = resultsByBuilder[builderId];
- for (var i = 0; i < results.length; i++) {
- results[i].build = new TestBuild(this._repositories, this._builders, results[i]);
- }
+ var resultsByTest = resultsByBuilder[builderId];
+ var results;
+ for (var testId in resultsByTest)
+ results = resultsByTest[testId];
+ if (!results)
+ continue;
- var sortedResults = results.sort(function (result1, result2) { return result1.build.time() - result2.build.time(); });
- var cells = new Array(sortedResults.length);
var builder = this._builders[builderId];
- for (var i = 0; i < sortedResults.length; i++)
- cells[i] = this._createResultCell(builder.master, builder.name, sortedResults[i], sortedResults[i - 1]);
-
- var passCount = cells.filter(function (cell) { return cell.className == 'PASS'; }).length;
- var passingRate = Math.round(passCount / cells.length * 100) + '%';
- table.appendChild(element('tr', [element('th', [builder.name]), element('td', {'class': 'passingRate'}, [passingRate]),
- element('td', cells)]));
// FIXME: Add a master name if there is more than one.
+ table.appendChild(this._createTestResultRow(builder.name, results, builder));
}
section.appendChild(table);
}
+TestResultsView._createTestResultRow = function (title, results, builder) {
+ for (var i = 0; i < results.length; i++)
+ results[i].build = new TestBuild(this._repositories, this._builders, results[i]);
+
+ var sortedResults = results.sort(function (result1, result2) { return result1.build.time() - result2.build.time(); });
+ var cells = new Array(sortedResults.length);
+ for (var i = 0; i < sortedResults.length; i++)
+ cells[i] = this._createResultCell(builder.master, builder.name, sortedResults[i], sortedResults[i - 1]);
+
+ var passCount = cells.filter(function (cell) { return cell.className == 'PASS'; }).length;
+ var passingRate = Math.round(passCount / cells.length * 100) + '%';
+ return element('tr', [element('th', ['' + title]), element('td', {'class': 'passingRate'}, [passingRate]), element('td', cells)]);
+}
+
TestResultsView.fetchTest = function (testName) {
if (this._tests.indexOf(testName) >= 0)
return;
@@ -321,20 +185,20 @@
section.parentNode.removeChild(section);
event.preventDefault();
});
- var section = element('section', {'id': testName, 'class': 'testResults'}, [closeButton]);
+ var section = element('section', {'id': testName, 'class': 'testResults'}, [closeButton, 'Loading...']);
- document.getElementById('container').appendChild(section);
+ document.getElementById('testView').appendChild(section);
var xhr = new XMLHttpRequest();
xhr.open("GET", 'api/results.php?test=' + testName, true);
xhr._onload_ = function(event) {
var response = JSON.parse(xhr.response);
+ section.removeChild(section.lastChild); // Remove Loading...
if (response['status'] != 'OK') {
section.appendChild(text('Failed to load results for ' + testName + ': ' + response['status']));
return;
}
-
- self._populatePane(testName, response, section);
+ self._populateTestPane(testName, response, section);
}
xhr.send();
this._tests.push(testName);
@@ -351,16 +215,67 @@
TestResultsView.fetchTests = function (testNames, doNotUpdateHash) {
for (var i = 0; i < testNames.length; i++)
this.fetchTest(testNames[i], doNotUpdateHash);
- this.updateLocationHash();
+ if (!doNotUpdateHash)
+ this.updateLocationHash();
}
+TestResultsView._populateBuilderPane = function(builderName, results, section) {
+ var table = element('table', {'class': 'resultsTable'}, [element('caption', [builderName])]);
+ var resultsByBuilder = results['builders'];
+ var resultsByTests;
+ var builderId;
+ for (var currentBuilderId in resultsByBuilder) {
+ builderId = currentBuilderId;
+ resultsByTests = resultsByBuilder[builderId];
+ }
+ if (!resultsByTests)
+ return;
+
+ var builder = this._builders[builderId];
+ for (var testId in resultsByTests)
+ table.appendChild(this._createTestResultRow(this._availableTests[testId].name, resultsByTests[testId], builder));
+ section.appendChild(table);
+}
+
+TestResultsView.fetchFailingTestsForBuilder = function (builderName, numberOfDays, doNotUpdateHash) {
+ var section = element('section', {'class': 'testResults'}, ['Loading...']);
+
+ var container = document.getElementById('builderFailingTestsView');
+ container.innerHTML = '';
+ container.appendChild(section);
+
+ var self = this;
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", 'api/failing-tests.php?builder=' + escape(builderName) + '&' + 'days=' + numberOfDays, true);
+ xhr._onload_ = function(event) {
+ var response = JSON.parse(xhr.response);
+ section.innerHTML = '';
+ if (response['status'] != 'OK') {
+ section.appendChild(text('Failed to load results for ' + builderName + ': ' + response['status']));
+ return;
+ }
+ self._currentBuilder = builderName;
+ self._currentBuilderDays = numberOfDays;
+ self._populateBuilderPane(builderName, response, section);
+ if (!doNotUpdateHash)
+ self.updateLocationHash();
+ }
+ xhr.send();
+}
+
TestResultsView.updateLocationHash = function () {
var params = {
- 'tests': this._tests.join(',')
+ 'tests': this._tests.join(','),
+ 'builder': this._currentBuilder,
+ 'builderDays': this._currentBuilderDays,
};
var hash = '';
- for (var key in params)
- hash += decodeURIComponent(key) + '=' + decodeURIComponent(params[key]);
+ for (var key in params) {
+ var value = params[key];
+ if (value === null || value === undefined)
+ continue;
+ hash += '&' + decodeURIComponent(key) + '=' + decodeURIComponent(value);
+ }
location.hash = hash;
this._oldHash = location.hash;
}
@@ -371,7 +286,6 @@
return;
this._oldHash = newHash;
this._tests = [];
- document.getElementById('container').innerHTML = '';
this.loadTestsFromLocationHash();
}
@@ -384,10 +298,13 @@
var name = decodeURIComponent(component.substr(0, equalPosition));
parsed[name] = decodeURIComponent(component.substr(equalPosition + 1));
});
- if (!parsed['tests'])
- return;
var doNotUpdateHash = true;
- this.fetchTests(parsed['tests'].split(','), doNotUpdateHash);
+ if (parsed['tests']) {
+ document.getElementById('testView').innerHTML = '';
+ this.fetchTests(parsed['tests'].split(','), doNotUpdateHash);
+ }
+ if (parsed['builder'])
+ this.fetchFailingTestsForBuilder(parsed['builder'], parsed['builderDays'] || 5, doNotUpdateHash);
}
function fetchManifest(callback) {
@@ -410,9 +327,31 @@
var input = document.getElementById('testName');
input.autocompleter = new Autocompleter(input, testNames);
- TestResultsView.setBuilders(response['builders']);
- TestResultsView.setSlaves(response['slaves']);
- TestResultsView.setRepositories(response['repositories']);
+ var builderListView = document.getElementById('builderListView');
+ for (var builderId in response['builders'])
+ builderListView.appendChild(element('option', [text(response['builders'][builderId].name)]));
+
+ var builderDaysView = document.getElementById('builderDaysView');
+
+ function updateBuilderView() {
+ if (builderListView.value)
+ TestResultsView.fetchFailingTestsForBuilder(builderListView.value, builderDaysView.value);
+ }
+
+ builderListView.addEventListener('change', updateBuilderView);
+ builderDaysView.addEventListener('change', updateBuilderView);
+
+ function mapById(items) {
+ var results = {};
+ for (var i = 0; i < items.length; i++)
+ results[items[i].id] = items[i];
+ return results;
+ }
+
+ TestResultsView.setAvailableTests(mapById(response['tests']));
+ TestResultsView.setBuilders(mapById(response['builders']));
+ TestResultsView.setSlaves(mapById(response['slaves']));
+ TestResultsView.setRepositories(mapById(response['repositories']));
TestResultsView.loadTestsFromLocationHash();
});
Added: trunk/Websites/test-results/main.css (0 => 157659)
--- trunk/Websites/test-results/main.css (rev 0)
+++ trunk/Websites/test-results/main.css 2013-10-19 01:47:40 UTC (rev 157659)
@@ -0,0 +1,163 @@
+#testName {
+ width: 99%;
+ font-size: 1em;
+ outline: none;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ padding: 5px;
+}
+
+.actionBar {
+
+}
+
+.testResults {
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ padding: 5px;
+ margin: 10px 0px;
+ position: relative;
+}
+
+.closeButton {
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ width: 1em;
+ height: 1em;
+ stroke: #999;
+}
+
+.resultsTable {
+ font-size: small;
+ border-collapse: collapse;
+ border: 2px solid #fff;
+ padding: 0;
+ margin: 0;
+}
+
+.resultsTable caption {
+ font-size: large;
+ font-weight: normal;
+ text-align: left;
+ margin-bottom: 0.3em;
+ white-space: pre;
+}
+
+.resultsTable td,
+.resultsTable th {
+ border: 2px solid #fff;
+ padding: 0;
+ margin: 0;
+}
+
+.resultsTable th,
+.resultsTable .passingRate {
+ font-weight: normal;
+ padding-right: 10px;
+}
+
+.resultsTable th {
+ width: 15em;
+}
+
+.resultsTable .passingRate {
+ width: 3em;
+}
+
+.resultsTable .resultCell {
+ display: inline-block;
+ padding: 0.2em 0.2em;
+}
+
+.resultsTable a {
+ display: block;
+ width: 1em;
+ height: 1.5em;
+ border-radius: 3px;
+}
+
+.resultsTable .PASS a {
+ background-color: #0c3;
+}
+
+.resultsTable .TEXT a {
+ background-color: #c33;
+}
+
+.resultsTable .IMAGE a {
+ background-color: #3cf;
+}
+
+.resultsTable .TEXT.PASS a {
+ background-color: #cf3;
+}
+
+.resultsTable .CRASH a {
+ background-color: #f00;
+}
+
+.candidateWindow {
+ z-index: 999;
+ position: absolute;
+ background: white;
+ color: black;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ margin: 5px 0 0 0;
+ padding: 5px;
+ font-size: 1em;
+ list-style: none;
+}
+
+.candidateWindow em {
+ background-color: #ccc;
+ font-style: normal;
+}
+
+.candidateWindow .selected {
+ background-color: #0cf;
+ color: white;
+}
+
+#tooltipContainer {
+ position: absolute;
+}
+
+.tooltip {
+ position: relative;
+ border-radius: 5px;
+ padding: 5px;
+ opacity: 0.9;
+ background: #333;
+ color: #eee;
+ font-size: small;
+ line-height: 130%;
+}
+
+.tooltip:after {
+ position: absolute;
+ width: 0;
+ height: 0;
+ left: 50%;
+ margin-left: -9px;
+ bottom: -19px;
+ content: "";
+ display: block;
+ border-style: solid;
+ border-width: 10px;
+ border-color: #333 transparent transparent transparent;
+}
+
+.tooltip ul,
+.tooltip li {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+.tooltip a {
+ color: white;
+ text-shadow: none;
+ text-decoration: underline;
+}