From 9e52d1ddac7fd4e54c0744a00aae6786081267af Mon Sep 17 00:00:00 2001
From: wei <>
Date: Sun, 25 Mar 2007 23:19:26 +0000
Subject: Update Selenium to 0.8.2
---
tests/test_tools/selenium/core/SeleniumLog.html | 14 +-
.../selenium/core/TestRunner-splash.html | 1 +
.../selenium/core/scripts/find_matching_child.js | 16 +-
.../test_tools/selenium/core/scripts/htmlutils.js | 411 +++--
.../selenium/core/scripts/injection.html | 19 +-
.../selenium/core/scripts/selenium-api.js | 1454 ++++++++++-------
.../selenium/core/scripts/selenium-browserbot.js | 1634 +++++++++++++-------
.../core/scripts/selenium-browserdetect.js | 48 +-
.../core/scripts/selenium-commandhandlers.js | 375 ++---
.../core/scripts/selenium-executionloop.js | 63 +-
.../selenium/core/scripts/selenium-logging.js | 30 +-
.../selenium/core/scripts/selenium-remoterunner.js | 466 ++++++
.../selenium/core/scripts/selenium-testrunner.js | 516 ++++---
.../selenium/core/scripts/selenium-version.js | 4 +-
tests/test_tools/selenium/core/selenium-test.css | 43 +
tests/test_tools/selenium/core/selenium.css | 44 +-
tests/test_tools/selenium/php/TestRunner.php | 3 +-
tests/test_tools/selenium/php/selenium.php | 2 +-
tests/test_tools/selenium/prado-functional-test.js | 74 +-
19 files changed, 3398 insertions(+), 1819 deletions(-)
create mode 100644 tests/test_tools/selenium/core/scripts/selenium-remoterunner.js
create mode 100644 tests/test_tools/selenium/core/selenium-test.css
(limited to 'tests/test_tools')
diff --git a/tests/test_tools/selenium/core/SeleniumLog.html b/tests/test_tools/selenium/core/SeleniumLog.html
index dfa0080a..dffa184f 100644
--- a/tests/test_tools/selenium/core/SeleniumLog.html
+++ b/tests/test_tools/selenium/core/SeleniumLog.html
@@ -2,6 +2,7 @@
Selenium Log Console
+
@@ -15,8 +16,6 @@ var logLevels = {
error: 3
};
-var logLevelThreshold = null;
-
function getThresholdLevel() {
var buttons = document.getElementById('logLevelChooser').level;
for (var i = 0; i < buttons.length; i++) {
@@ -27,10 +26,9 @@ function getThresholdLevel() {
}
function setThresholdLevel(logLevel) {
- logLevelThreshold = logLevel;
var buttons = document.getElementById('logLevelChooser').level;
for (var i = 0; i < buttons.length; i++) {
- if (buttons[i].value==logLevel) {
+ if (buttons[i].value==logLevel) {
buttons[i].checked = true;
}
else {
@@ -40,9 +38,7 @@ function setThresholdLevel(logLevel) {
}
function append(message, logLevel) {
- if (logLevelThreshold==null) {
- logLevelThreshold = getThresholdLevel();
- }
+ var logLevelThreshold = getThresholdLevel();
if (logLevels[logLevel] < logLevels[logLevelThreshold]) {
return;
}
@@ -64,9 +60,9 @@ function append(message, logLevel) {
value="error" />
-
-
Selenium Log Console
diff --git a/tests/test_tools/selenium/core/TestRunner-splash.html b/tests/test_tools/selenium/core/TestRunner-splash.html
index 205bb8ef..1c32dd79 100644
--- a/tests/test_tools/selenium/core/TestRunner-splash.html
+++ b/tests/test_tools/selenium/core/TestRunner-splash.html
@@ -15,6 +15,7 @@ Copyright 2005 ThoughtWorks, Inc
-->
+
diff --git a/tests/test_tools/selenium/core/scripts/find_matching_child.js b/tests/test_tools/selenium/core/scripts/find_matching_child.js
index 197d1032..fbf35b75 100644
--- a/tests/test_tools/selenium/core/scripts/find_matching_child.js
+++ b/tests/test_tools/selenium/core/scripts/find_matching_child.js
@@ -15,8 +15,8 @@
*
*/
-Element.findMatchingChildren = function(element, selector) {
- var matches = $A([]);
+elementFindMatchingChildren = function(element, selector) {
+ var matches = [];
var childCount = element.childNodes.length;
for (var i=0; i element
function getInputValue(inputElement) {
- if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
- inputElement.type.toUpperCase() == 'RADIO')
- {
- return (inputElement.checked ? 'on' : 'off');
+ if (inputElement.type) {
+ if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
+ inputElement.type.toUpperCase() == 'RADIO')
+ {
+ return (inputElement.checked ? 'on' : 'off');
+ }
+ }
+ if (inputElement.value == null) {
+ throw new SeleniumError("This element has no value; is it really a form field?");
}
return inputElement.value;
}
/* Fire an event in a browser-compatible manner */
-function triggerEvent(element, eventType, canBubble) {
+function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
if (element.fireEvent) {
- element.fireEvent('on' + eventType);
+ var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
+ element.fireEvent('on' + eventType, evt);
}
else {
var evt = document.createEvent('HTMLEvents');
+
+ try {
+ evt.shiftKey = shiftKeyDown;
+ evt.metaKey = metaKeyDown;
+ evt.altKey = altKeyDown;
+ evt.ctrlKey = controlKeyDown;
+ } catch (e) {
+ // On Firefox 1.0, you can only set these during initMouseEvent or initKeyEvent
+ // we'll have to ignore them here
+ LOG.exception(e);
+ }
+
evt.initEvent(eventType, canBubble, true);
element.dispatchEvent(evt);
}
@@ -179,14 +284,23 @@ function getKeyCodeFromKeySequence(keySequence) {
if (match != null) {
return match[0];
}
- throw SeleniumError("invalid keySequence");
+ throw new SeleniumError("invalid keySequence");
}
-function triggerKeyEvent(element, eventType, keySequence, canBubble) {
+function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
+ var evt = element.ownerDocument.createEventObject();
+ evt.shiftKey = shiftKeyDown;
+ evt.metaKey = metaKeyDown;
+ evt.altKey = altKeyDown;
+ evt.ctrlKey = controlKeyDown;
+ return evt;
+}
+
+function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
var keycode = getKeyCodeFromKeySequence(keySequence);
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
if (element.fireEvent) {
- keyEvent = element.ownerDocument.createEventObject();
+ var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
keyEvent.keyCode = keycode;
element.fireEvent('on' + eventType, keyEvent);
}
@@ -194,82 +308,24 @@ function triggerKeyEvent(element, eventType, keySequence, canBubble) {
var evt;
if (window.KeyEvent) {
evt = document.createEvent('KeyEvents');
- evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode);
+ evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, keycode, keycode);
} else {
evt = document.createEvent('UIEvents');
+
+ evt.shiftKey = shiftKeyDown;
+ evt.metaKey = metaKeyDown;
+ evt.altKey = altKeyDown;
+ evt.ctrlKey = controlKeyDown;
+
evt.initUIEvent(eventType, true, true, window, 1);
evt.keyCode = keycode;
+ evt.which = keycode;
}
element.dispatchEvent(evt);
}
}
-/* Fire a mouse event in a browser-compatible manner */
-function triggerMouseEvent(element, eventType, canBubble, clientX, clientY) {
- clientX = clientX ? clientX : 0;
- clientY = clientY ? clientY : 0;
-
- // TODO: set these attributes -- they don't seem to be needed by the initial test cases, but that could change...
- var screenX = 0;
- var screenY = 0;
-
- canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
- if (element.fireEvent) {
- LOG.error("element has fireEvent");
- if (!screenX && !screenY && !clientX && !clientY) {
- element.fireEvent('on' + eventType);
- }
- else {
- var ieEvent = element.ownerDocument.createEventObject();
- ieEvent.detail = 0;
- ieEvent.screenX = screenX;
- ieEvent.screenY = screenY;
- ieEvent.clientX = clientX;
- ieEvent.clientY = clientY;
- ieEvent.ctrlKey = false;
- ieEvent.altKey = false;
- ieEvent.shiftKey = false;
- ieEvent.metaKey = false;
- ieEvent.button = 1;
- ieEvent.relatedTarget = null;
-
- // when we go this route, window.event is never set to contain the event we have just created.
- // ideally we could just slide it in as follows in the try-block below, but this normally
- // doesn't work. This is why I try to avoid this code path, which is only required if we need to
- // set attributes on the event (e.g., clientX).
- try {
- window.event = ieEvent;
- }
- catch(e) {
- // getting an "Object does not support this action or property" error. Save the event away
- // for future reference.
- // TODO: is there a way to update window.event?
-
- // work around for http://jira.openqa.org/browse/SEL-280 -- make the event available somewhere:
- selenium.browserbot.getCurrentWindow().selenium_event = ieEvent;
- }
- element.fireEvent('on' + eventType, ieEvent);
- }
- }
- else {
- LOG.error("element doesn't have fireEvent");
- var evt = document.createEvent('MouseEvents');
- if (evt.initMouseEvent)
- {
- LOG.error("element has initMouseEvent");
- //Safari
- evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY, false, false, false, false, 0, null)
- }
- else {
- LOG.error("element doesen't has initMouseEvent");
- // TODO we should be initialising other mouse-event related attributes here
- evt.initEvent(eventType, canBubble, true);
- }
- element.dispatchEvent(evt);
- }
-}
-
function removeLoadListener(element, command) {
LOG.info('Removing loadListenter for ' + element + ', ' + command);
if (window.removeEventListener)
@@ -280,10 +336,13 @@ function removeLoadListener(element, command) {
function addLoadListener(element, command) {
LOG.info('Adding loadListenter for ' + element + ', ' + command);
+ var augmentedCommand = function() {
+ command.call(this, element);
+ }
if (window.addEventListener && !browserVersion.isOpera)
- element.addEventListener("load", command, true);
+ element.addEventListener("load", augmentedCommand, true);
else if (window.attachEvent)
- element.attachEvent("onload", command);
+ element.attachEvent("onload", augmentedCommand);
}
/**
@@ -306,10 +365,160 @@ function getDocumentBase(doc) {
return "";
}
+function getTagName(element) {
+ var tagName;
+ if (element && element.tagName && element.tagName.toLowerCase) {
+ tagName = element.tagName.toLowerCase();
+ }
+ return tagName;
+}
+
+function absolutify(url, baseUrl) {
+ /** returns a relative url in its absolute form, given by baseUrl.
+ *
+ * This function is a little odd, because it can take baseUrls that
+ * aren't necessarily directories. It uses the same rules as the HTML
+ * <base> tag; if the baseUrl doesn't end with "/", we'll assume
+ * that it points to a file, and strip the filename off to find its
+ * base directory.
+ *
+ * So absolutify("foo", "http://x/bar") will return "http://x/foo" (stripping off bar),
+ * whereas absolutify("foo", "http://x/bar/") will return "http://x/bar/foo" (preserving bar).
+ * Naturally absolutify("foo", "http://x") will return "http://x/foo", appropriately.
+ *
+ * @param url the url to make absolute; if this url is already absolute, we'll just return that, unchanged
+ * @param baseUrl the baseUrl from which we'll absolutify, following the rules above.
+ * @return 'url' if it was already absolute, or the absolutized version of url if it was not absolute.
+ */
+
+ // DGF isn't there some library we could use for this?
+
+ if (/^\w+:/.test(url)) {
+ // it's already absolute
+ return url;
+ }
+
+ var loc;
+ try {
+ loc = parseUrl(baseUrl);
+ } catch (e) {
+ // is it an absolute windows file path? let's play the hero in that case
+ if (/^\w:\\/.test(baseUrl)) {
+ baseUrl = "file:///" + baseUrl.replace(/\\/g, "/");
+ loc = parseUrl(baseUrl);
+ } else {
+ throw new SeleniumError("baseUrl wasn't absolute: " + baseUrl);
+ }
+ }
+ loc.search = null;
+ loc.hash = null;
+
+ // if url begins with /, then that's the whole pathname
+ if (/^\//.test(url)) {
+ loc.pathname = url;
+ var result = reassembleLocation(loc);
+ return result;
+ }
+
+ // if pathname is null, then we'll just append "/" + the url
+ if (!loc.pathname) {
+ loc.pathname = "/" + url;
+ var result = reassembleLocation(loc);
+ return result;
+ }
+
+ // if pathname ends with /, just append url
+ if (/\/$/.test(loc.pathname)) {
+ loc.pathname += url;
+ var result = reassembleLocation(loc);
+ return result;
+ }
+
+ // if we're here, then the baseUrl has a pathname, but it doesn't end with /
+ // in that case, we replace everything after the final / with the relative url
+ loc.pathname = loc.pathname.replace(/[^\/\\]+$/, url);
+ var result = reassembleLocation(loc);
+ return result;
+
+}
+
+var URL_REGEX = /^((\w+):\/\/)(([^:]+):?([^@]+)?@)?([^\/\?:]*):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(.+)?/;
+
+function parseUrl(url) {
+ var fields = ['url', null, 'protocol', null, 'username', 'password', 'host', 'port', 'pathname', 'search', 'hash'];
+ var result = URL_REGEX.exec(url);
+ if (!result) {
+ throw new SeleniumError("Invalid URL: " + url);
+ }
+ var loc = new Object();
+ for (var i = 0; i < fields.length; i++) {
+ var field = fields[i];
+ if (field == null) {
+ continue;
+ }
+ loc[field] = result[i];
+ }
+ return loc;
+}
+
+function reassembleLocation(loc) {
+ if (!loc.protocol) {
+ throw new Error("Not a valid location object: " + o2s(loc));
+ }
+ var protocol = loc.protocol;
+ protocol = protocol.replace(/:$/, "");
+ var url = protocol + "://";
+ if (loc.username) {
+ url += loc.username;
+ if (loc.password) {
+ url += ":" + loc.password;
+ }
+ url += "@";
+ }
+ if (loc.host) {
+ url += loc.host;
+ }
+
+ if (loc.port) {
+ url += ":" + loc.port;
+ }
+
+ if (loc.pathname) {
+ url += loc.pathname;
+ }
+
+ if (loc.search) {
+ url += "?" + loc.search;
+ }
+ if (loc.hash) {
+ var hash = loc.hash;
+ hash = loc.hash.replace(/^#/, "");
+ url += "#" + hash;
+ }
+ return url;
+}
+
+function canonicalize(url) {
+ var tempLink = window.document.createElement("link");
+ tempLink.href = url; // this will canonicalize the href
+ return tempLink.href;
+}
+
+function extractExceptionMessage(ex) {
+ if (ex == null) return "null exception";
+ if (ex.message != null) return ex.message;
+ if (ex.toString && ex.toString() != null) return ex.toString();
+}
+
+
function describe(object, delimiter) {
var props = new Array();
for (var prop in object) {
- props.push(prop + " -> " + object[prop]);
+ try {
+ props.push(prop + " -> " + object[prop]);
+ } catch (e) {
+ props.push(prop + " -> [htmlutils: ack! couldn't read this property! (Permission Denied?)]");
+ }
}
return props.join(delimiter || '\n');
}
@@ -502,28 +711,27 @@ function SeleniumError(message) {
return error;
}
-var Effect = new Object();
-
-Object.extend(Effect, {
- highlight : function(element) {
- var highLightColor = "yellow";
- if (element.originalColor == undefined) { // avoid picking up highlight
- element.originalColor = Element.getStyle(element, "background-color");
- }
- Element.setStyle(element, {"background-color" : highLightColor});
- window.setTimeout(function() {
+function highlight(element) {
+ var highLightColor = "yellow";
+ if (element.originalColor == undefined) { // avoid picking up highlight
+ element.originalColor = elementGetStyle(element, "background-color");
+ }
+ elementSetStyle(element, {"backgroundColor" : highLightColor});
+ window.setTimeout(function() {
+ try {
//if element is orphan, probably page of it has already gone, so ignore
if (!element.parentNode) {
return;
}
- Element.setStyle(element, {"background-color" : element.originalColor});
- }, 200);
- }
-});
+ elementSetStyle(element, {"backgroundColor" : element.originalColor});
+ } catch (e) {} // DGF unhighlighting is very dangerous and low priority
+ }, 200);
+}
+
// for use from vs.2003 debugger
-function objToString(obj) {
+function o2s(obj) {
var s = "";
for (key in obj) {
var line = key + "->" + obj[key];
@@ -535,7 +743,7 @@ function objToString(obj) {
var seenReadyStateWarning = false;
-function openSeparateApplicationWindow(url) {
+function openSeparateApplicationWindow(url, suppressMozillaWarning) {
// resize the Selenium window itself
window.resizeTo(1200, 500);
window.moveTo(window.screenX, 0);
@@ -560,7 +768,7 @@ function openSeparateApplicationWindow(url) {
}
- if (window.document.readyState == null && !seenReadyStateWarning) {
+ if (!suppressMozillaWarning && window.document.readyState == null && !seenReadyStateWarning) {
alert("Beware! Mozilla bug 300992 means that we can't always reliably detect when a new page has loaded. Install the Selenium IDE extension or the readyState extension available from selenium.openqa.org to make page load detection more reliable.");
seenReadyStateWarning = true;
}
@@ -568,8 +776,8 @@ function openSeparateApplicationWindow(url) {
return appWindow;
}
-var URLConfiguration = Class.create();
-Object.extend(URLConfiguration.prototype, {
+var URLConfiguration = classCreate();
+objectExtend(URLConfiguration.prototype, {
initialize: function() {
},
_isQueryParameterTrue: function (name) {
@@ -615,6 +823,11 @@ Object.extend(URLConfiguration.prototype, {
isMultiWindowMode:function() {
return this._isQueryParameterTrue('multiWindow');
+ },
+
+ getBaseUrl:function() {
+ return this._getQueryParameter('baseUrl');
+
}
});
diff --git a/tests/test_tools/selenium/core/scripts/injection.html b/tests/test_tools/selenium/core/scripts/injection.html
index d41fbe69..a75c7211 100644
--- a/tests/test_tools/selenium/core/scripts/injection.html
+++ b/tests/test_tools/selenium/core/scripts/injection.html
@@ -1,21 +1,22 @@
+
-
@@ -49,6 +49,7 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
diff --git a/tests/test_tools/selenium/php/selenium.php b/tests/test_tools/selenium/php/selenium.php
index c9d403f1..474c0d6d 100644
--- a/tests/test_tools/selenium/php/selenium.php
+++ b/tests/test_tools/selenium/php/selenium.php
@@ -142,7 +142,7 @@ class SeleneseInterpreter
$command = array($func, $ID, $value);
if(is_int(strpos(strtolower($func), 'visible')))
- $this->addCommand(array('pause','500',''),$trace);
+ $this->addCommand(array('pause','250',''),$trace);
return $this->addCommand($command, $trace);
}
diff --git a/tests/test_tools/selenium/prado-functional-test.js b/tests/test_tools/selenium/prado-functional-test.js
index 4cda378c..9d4446f0 100644
--- a/tests/test_tools/selenium/prado-functional-test.js
+++ b/tests/test_tools/selenium/prado-functional-test.js
@@ -1,19 +1,45 @@
-Object.extend(HtmlTestRunner.prototype, {
- loadSuiteFrame: function() {
- if (selenium == null) {
- selenium = Selenium.createForWindow(this._getApplicationWindow());
- this._registerCommandHandlers();
- }
- this.controlPanel.setHighlightOption();
- //var testSuiteName = this.controlPanel.getTestSuiteName();
- var testSuiteName = document.location+'?testSuites';
- if (testSuiteName) {
- suiteFrame.load(testSuiteName, this._onloadTestSuite.bind(this));
- }
+
+objectExtend(HtmlTestRunnerControlPanel.prototype, {
+ getTestSuiteName: function() {
+ return document.location+'?testSuites'; //this._getQueryParameter("test");
}
});
-Object.extend(HtmlTestRunnerControlPanel.prototype, {
+SeleniumFrame.prototype._setLocation = function(location) {
+ /* var isChrome = browserVersion.isChrome || false;
+ var isHTA = browserVersion.isHTA || false;
+ // DGF TODO multiWindow
+ location += "?thisIsChrome=" + isChrome + "&thisIsHTA=" + isHTA;*/
+ if (browserVersion.isSafari) {
+ // safari doesn't reload the page when the location equals to current location.
+ // hence, set the location to blank so that the page will reload automatically.
+ this.frame.src = "about:blank";
+ this.frame.src = location;
+ } else {
+ this.frame.contentWindow.location.replace(location);
+ }
+ };
+
+SeleniumFrame.prototype._attachStylesheet = function()
+{
+ var base_url = script_base_url;
+ var d = this.getDocument();
+ var head = d.getElementsByTagName('head').item(0);
+ var styleLink = d.createElement("link");
+ styleLink.rel = "stylesheet";
+ styleLink.type = "text/css";
+ styleLink.href = base_url + "core/selenium-test.css";
+ head.appendChild(styleLink);
+};
+
+HtmlTestFrame.prototype._setLocation = SeleniumFrame.prototype._setLocation;
+HtmlTestSuiteFrame.prototype._setLocation = SeleniumFrame.prototype._setLocation;
+
+HtmlTestFrame.prototype._attachStylesheet = SeleniumFrame.prototype._attachStylesheet;
+HtmlTestSuiteFrame.prototype._attachStylesheet = SeleniumFrame.prototype._attachStylesheet;
+
+
+objectExtend(HtmlTestRunnerControlPanel.prototype, {
_parseQueryParameter: function() {
var tempRunInterval = this._getQueryParameter("runInterval");
if (tempRunInterval) {
@@ -37,7 +63,7 @@ Selenium.prototype.getAttribute = function(target) {
*/
Selenium.prototype.isVisible = function(locator) {
var element;
- element = this.page().findElement(locator);
+ element = this.page().findElement(locator);
if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
var visibility = element.style["visibility"];
@@ -64,13 +90,13 @@ Selenium.prototype._isDisplayed = function(element) {
return true;
};
-Selenium.prototype.assertEmptySelection = function(selectLocator, optionLocator)
+Selenium.prototype.assertEmptySelection = function(selectLocator, optionLocator)
{
/**
* Verifies that the selected option of a drop-down satisfies the optionSpecifier.
- *
+ *
* See the select command for more information about option locators.
- *
+ *
* @param selectLocator an element locator identifying a drop-down menu
* @param optionLocator an option locator, typically just an option label (e.g. "John Smith")
*/
@@ -78,9 +104,9 @@ Selenium.prototype.assertEmptySelection = function(selectLocator, optionLocator)
var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
return element.selectedIndex == -1;
}
-
-Object.extend(HtmlTestSuite.prototype, {
+
+objectExtend(HtmlTestSuite.prototype, {
_onTestSuiteComplete: function() {
this.markDone();
var result = new TestResult(this.failed, this.getTestTable());
@@ -89,7 +115,7 @@ Object.extend(HtmlTestSuite.prototype, {
});
-
+
// Post the results to a servlet, CGI-script, etc. The URL of the
// results-handler defaults to "/postResults", but an alternative location
@@ -208,10 +234,10 @@ function parse_resultCell(resultCell,rowNum,form)
function get_color_status(element)
{
- var color = element.getAttribute("bgcolor");
- if(color == FeedbackColors.passColor) return "passed";
- if(color == FeedbackColors.failColor) return "failed";
- if(color == FeedbackColors.doneColor) return "done";
+ var color = element.className
+ if(color == 'status_passed') return "passed";
+ if(color == 'status_failed') return "failed";
+ if(color == 'status_done') return "done";
return "";
}
--
cgit v1.2.3