| OLD | NEW | 
 | (Empty) | 
|    1 // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file |  | 
|    2 // for details. All rights reserved. Use of this source code is governed by a |  | 
|    3 // BSD-style license that can be found in the LICENSE file. |  | 
|    4  |  | 
|    5 /** |  | 
|    6  * This configuration can be used to rerun selected tests, as well |  | 
|    7  * as see diagnostic output from tests. It runs each test in its own |  | 
|    8  * IFrame, so the configuration consists of two parts - a 'master' |  | 
|    9  * config that manages all the tests, and a 'slave' config for the |  | 
|   10  * IFrame that runs the individual tests. |  | 
|   11  */ |  | 
|   12 #library('interactive_config'); |  | 
|   13  |  | 
|   14 // TODO(gram) - add options for: remove IFrame on done/keep |  | 
|   15 // IFrame for failed tests/keep IFrame for all tests. |  | 
|   16  |  | 
|   17 #import('dart:html'); |  | 
|   18 #import('unittest.dart'); |  | 
|   19  |  | 
|   20 /** The messages exchanged between master and slave. */ |  | 
|   21  |  | 
|   22 class _Message { |  | 
|   23   static final START = 'start'; |  | 
|   24   static final LOG = 'log'; |  | 
|   25   static final STACK = 'stack'; |  | 
|   26   static final PASS = 'pass'; |  | 
|   27   static final FAIL = 'fail'; |  | 
|   28   static final ERROR = 'error'; |  | 
|   29  |  | 
|   30   String messageType; |  | 
|   31   int elapsed; |  | 
|   32   String body; |  | 
|   33  |  | 
|   34   static String text(String messageType, |  | 
|   35                      [int elapsed = 0, String body = '']) => |  | 
|   36       '$messageType $elapsed $body'; |  | 
|   37  |  | 
|   38   _Message(this.messageType, [this.elapsed = 0, this.body = '']); |  | 
|   39  |  | 
|   40   _Message.fromString(String msg) { |  | 
|   41     int idx = msg.indexOf(' '); |  | 
|   42     messageType = msg.substring(0, idx); |  | 
|   43     ++idx; |  | 
|   44     int idx2 = msg.indexOf(' ', idx); |  | 
|   45     elapsed = Math.parseInt(msg.substring(idx, idx2)); |  | 
|   46     ++idx2; |  | 
|   47     body = msg.substring(idx2); |  | 
|   48   } |  | 
|   49  |  | 
|   50   String toString() => text(messageType, elapsed, body); |  | 
|   51 } |  | 
|   52  |  | 
|   53 /** |  | 
|   54  * The slave configuration that is used to run individual tests in |  | 
|   55  * an IFrame and post the results back to the master. In principle |  | 
|   56  * this can run more than one test in the IFrame but currently only |  | 
|   57  * one is used. |  | 
|   58  */ |  | 
|   59 class SlaveInteractiveHtmlConfiguration extends Configuration { |  | 
|   60   // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |  | 
|   61   EventListener _onErrorClosure; |  | 
|   62  |  | 
|   63   /** The window to which results must be posted. */ |  | 
|   64   Window masterWindow; |  | 
|   65  |  | 
|   66   /** The time at which tests start. */ |  | 
|   67   Map<int,Date> _testStarts; |  | 
|   68  |  | 
|   69   SlaveInteractiveHtmlConfiguration() : |  | 
|   70       _testStarts = new Map<int,Date>(); |  | 
|   71  |  | 
|   72   /** Don't start running tests automatically. */ |  | 
|   73   get autoStart() => false; |  | 
|   74  |  | 
|   75   void onInit() { |  | 
|   76     _onErrorClosure = |  | 
|   77         (e) => handleExternalError(e, '(DOM callback has errors)'); |  | 
|   78  |  | 
|   79     /** |  | 
|   80      *  The master posts a 'start' message to kick things off, |  | 
|   81      *  which is handled by this handler. It saves the master |  | 
|   82      *  window, gets the test ID from the query parameter in the |  | 
|   83      *  IFrame URL, sets that as a solo test and starts test execution. |  | 
|   84      */ |  | 
|   85     window.on.message.add((MessageEvent e) { |  | 
|   86       // Get the result, do any logging, then do a pass/fail. |  | 
|   87       var m = new _Message.fromString(e.data); |  | 
|   88       if (m.messageType == _Message.START) { |  | 
|   89         masterWindow = e.source; |  | 
|   90         String search = window.location.search; |  | 
|   91         int pos = search.indexOf('t='); |  | 
|   92         String ids = search.substring(pos+2); |  | 
|   93         int id = Math.parseInt(ids); |  | 
|   94         setSoloTest(id); |  | 
|   95         runTests(); |  | 
|   96       } |  | 
|   97     }); |  | 
|   98   } |  | 
|   99  |  | 
|  100   void onStart() { |  | 
|  101     // Listen for uncaught errors. |  | 
|  102     window.on.error.add(_onErrorClosure); |  | 
|  103   } |  | 
|  104  |  | 
|  105   /** Record the start time of the test. */ |  | 
|  106   void onTestStart(TestCase testCase) { |  | 
|  107     super.onTestStart(testCase); |  | 
|  108     _testStarts[testCase.id]= new Date.now(); |  | 
|  109   } |  | 
|  110  |  | 
|  111   /** |  | 
|  112    * Tests can call [log] for diagnostic output. These log |  | 
|  113    * messages in turn get passed to this method, which adds |  | 
|  114    * a timestamp and posts them back to the master window. |  | 
|  115    */ |  | 
|  116   void logMessage(TestCase testCase, String message) { |  | 
|  117     int elapsed; |  | 
|  118     if (testCase == null) { |  | 
|  119       elapsed = -1; |  | 
|  120     } else { |  | 
|  121       Date end = new Date.now(); |  | 
|  122       elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |  | 
|  123     } |  | 
|  124     masterWindow.postMessage( |  | 
|  125       _Message.text(_Message.LOG, elapsed, message).toString(), '*'); |  | 
|  126   } |  | 
|  127  |  | 
|  128   /** |  | 
|  129    * Get the elapsed time for the test, anbd post the test result |  | 
|  130    * back to the master window. If the test failed due to an exception |  | 
|  131    * the stack is posted back too (before the test result). |  | 
|  132    */ |  | 
|  133   void onTestResult(TestCase testCase) { |  | 
|  134     super.onTestResult(testCase); |  | 
|  135     Date end = new Date.now(); |  | 
|  136     int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |  | 
|  137     if (testCase.stackTrace != null) { |  | 
|  138       masterWindow.postMessage( |  | 
|  139           _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); |  | 
|  140     } |  | 
|  141     masterWindow.postMessage( |  | 
|  142         _Message.text(testCase.result, elapsed, testCase.message), '*'); |  | 
|  143   } |  | 
|  144  |  | 
|  145   void onDone(int passed, int failed, int errors, List<TestCase> results, |  | 
|  146               String uncaughtError) { |  | 
|  147     window.on.error.remove(_onErrorClosure); |  | 
|  148   } |  | 
|  149 } |  | 
|  150  |  | 
|  151 /** |  | 
|  152  * The master configuration runs in the top-level window; it wraps the tests |  | 
|  153  * in new functions that create slave IFrames and run the real tests. |  | 
|  154  */ |  | 
|  155 class MasterInteractiveHtmlConfiguration extends Configuration { |  | 
|  156   Map<int,Date> _testStarts; |  | 
|  157   // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |  | 
|  158   EventListener _onErrorClosure; |  | 
|  159  |  | 
|  160   /** The stack that was posted back from the slave, if any. */ |  | 
|  161   String _stack; |  | 
|  162  |  | 
|  163   int _testTime; |  | 
|  164   /** |  | 
|  165    * Whether or not we have already wrapped the TestCase test functions |  | 
|  166    * in new closures that instead create an IFrame and get it to run the |  | 
|  167    * test. |  | 
|  168    */ |  | 
|  169   bool _doneWrap = false; |  | 
|  170  |  | 
|  171   /** |  | 
|  172    * We use this to make a single closure from _handleMessage so we |  | 
|  173    * can remove the handler later. |  | 
|  174    */ |  | 
|  175   Function _messageHandler; |  | 
|  176  |  | 
|  177   MasterInteractiveHtmlConfiguration() : |  | 
|  178       _testStarts = new Map<int,Date>(); |  | 
|  179  |  | 
|  180   // We need to block until the test is done, so we make a |  | 
|  181   // dummy async callback that we will use to flag completion. |  | 
|  182   Function completeTest = null; |  | 
|  183  |  | 
|  184   wrapTest(TestCase testCase) { |  | 
|  185     String baseUrl = window.location.toString(); |  | 
|  186     String url = '${baseUrl}?t=${testCase.id}'; |  | 
|  187     return () { |  | 
|  188       // Rebuild the slave IFrame. |  | 
|  189       Element slaveDiv = document.query('#slave'); |  | 
|  190       slaveDiv.nodes.clear(); |  | 
|  191       IFrameElement slave = new Element.html(""" |  | 
|  192           <iframe id='slaveFrame${testCase.id}' src='$url' style='display:none'> |  | 
|  193           </iframe>"""); |  | 
|  194       slaveDiv.nodes.add(slave); |  | 
|  195       completeTest = expectAsync0((){ }); |  | 
|  196       // Kick off the test when the IFrame is loaded. |  | 
|  197       slave.on.load.add((e) { |  | 
|  198         slave.contentWindow.postMessage(_Message.text(_Message.START), '*'); |  | 
|  199       }); |  | 
|  200     }; |  | 
|  201   } |  | 
|  202  |  | 
|  203   void _handleMessage(MessageEvent e) { |  | 
|  204     // Get the result, do any logging, then do a pass/fail. |  | 
|  205     var msg = new _Message.fromString(e.data); |  | 
|  206     if (msg.messageType == _Message.LOG) { |  | 
|  207       log(e.data); |  | 
|  208     } else if (msg.messageType == _Message.STACK) { |  | 
|  209       _stack = msg.body; |  | 
|  210     } else { |  | 
|  211       _testTime = msg.elapsed; |  | 
|  212       log(_Message.text(_Message.LOG, _testTime, 'Complete')); |  | 
|  213       if (msg.messageType == _Message.PASS) { |  | 
|  214         currentTestCase.pass(); |  | 
|  215       } else if (msg.messageType == _Message.FAIL) { |  | 
|  216         currentTestCase.fail(msg.body, _stack); |  | 
|  217       } else if (msg.messageType == _Message.ERROR) { |  | 
|  218         currentTestCase.error(msg.body, _stack); |  | 
|  219       } |  | 
|  220       completeTest(); |  | 
|  221     } |  | 
|  222   } |  | 
|  223  |  | 
|  224   void onInit() { |  | 
|  225     _messageHandler = _handleMessage; // We need to make just one closure. |  | 
|  226     _onErrorClosure = |  | 
|  227         (e) => handleExternalError(e, '(DOM callback has errors)'); |  | 
|  228     document.query('#group-divs').innerHTML = ""; |  | 
|  229   } |  | 
|  230  |  | 
|  231   void onStart() { |  | 
|  232     // Listen for uncaught errors. |  | 
|  233     window.on.error.add(_onErrorClosure); |  | 
|  234     if (!_doneWrap) { |  | 
|  235       _doneWrap = true; |  | 
|  236       for (int i = 0; i < testCases.length; i++) { |  | 
|  237         testCases[i].test = wrapTest(testCases[i]); |  | 
|  238         testCases[i].setUp = null; |  | 
|  239         testCases[i].tearDown = null; |  | 
|  240       } |  | 
|  241     } |  | 
|  242     window.on.message.add(_messageHandler); |  | 
|  243   } |  | 
|  244  |  | 
|  245   static final _notAlphaNumeric = const RegExp('[^a-z0-9A-Z]'); |  | 
|  246  |  | 
|  247   String _stringToDomId(String s) { |  | 
|  248     if (s.length == 0) { |  | 
|  249       return '-None-'; |  | 
|  250     } |  | 
|  251     return s.trim().replaceAll(_notAlphaNumeric, '-'); |  | 
|  252   } |  | 
|  253  |  | 
|  254   // Used for DOM element IDs for tests result list entries. |  | 
|  255   static final _testIdPrefix = 'test-'; |  | 
|  256   // Used for DOM element IDs for test log message lists. |  | 
|  257   static final _actionIdPrefix = 'act-'; |  | 
|  258   // Used for DOM element IDs for test checkboxes. |  | 
|  259   static final _selectedIdPrefix = 'selected-'; |  | 
|  260  |  | 
|  261   void onTestStart(TestCase testCase) { |  | 
|  262     var id = testCase.id; |  | 
|  263     _testStarts[testCase.id]= new Date.now(); |  | 
|  264     super.onTestStart(testCase); |  | 
|  265     _stack = null; |  | 
|  266     // Convert the group name to a DOM id. |  | 
|  267     String groupId = _stringToDomId(testCase.currentGroup); |  | 
|  268     // Get the div for the group. If it doesn't exist, |  | 
|  269     // create it. |  | 
|  270     var groupDiv = document.query('#$groupId'); |  | 
|  271     if (groupDiv == null) { |  | 
|  272       groupDiv = new Element.html(""" |  | 
|  273           <div class='test-describe' id='$groupId'> |  | 
|  274             <h2> |  | 
|  275               <input type='checkbox' checked='true' class='groupselect'> |  | 
|  276               Group: ${testCase.currentGroup} |  | 
|  277             </h2> |  | 
|  278             <ul class='tests'> |  | 
|  279             </ul> |  | 
|  280           </div>"""); |  | 
|  281       document.query('#group-divs').nodes.add(groupDiv); |  | 
|  282       groupDiv.query('.groupselect').on.click.add((e) { |  | 
|  283         var parent = document.query('#$groupId'); |  | 
|  284         InputElement cb = parent.query('.groupselect'); |  | 
|  285         var state = cb.checked; |  | 
|  286         var tests = parent.query('.tests'); |  | 
|  287         for (Element t in tests.elements) { |  | 
|  288           cb = t.query('.testselect'); |  | 
|  289           cb.checked = state; |  | 
|  290           var testId = Math.parseInt(t.id.substring(_testIdPrefix.length)); |  | 
|  291           if (state) { |  | 
|  292             enableTest(testId); |  | 
|  293           } else { |  | 
|  294             disableTest(testId); |  | 
|  295           } |  | 
|  296         } |  | 
|  297       }); |  | 
|  298     } |  | 
|  299     var list = groupDiv.query('.tests'); |  | 
|  300     var testItem = list.query('#$_testIdPrefix$id'); |  | 
|  301     if (testItem == null) { |  | 
|  302       // Create the li element for the test. |  | 
|  303       testItem = new Element.html(""" |  | 
|  304           <li id='$_testIdPrefix$id' class='test-it status-pending'> |  | 
|  305             <div class='test-info'> |  | 
|  306               <p class='test-title'> |  | 
|  307                 <input type='checkbox' checked='true' class='testselect'  |  | 
|  308                     id='$_selectedIdPrefix$id'> |  | 
|  309                 <span class='test-label'> |  | 
|  310                 <span class='timer-result test-timer-result'></span> |  | 
|  311                 <span class='test-name closed'>${testCase.description}</span> |  | 
|  312                 </span> |  | 
|  313               </p> |  | 
|  314             </div> |  | 
|  315             <div class='scrollpane'> |  | 
|  316               <ol class='test-actions' id='$_actionIdPrefix$id'></ol> |  | 
|  317             </div>           |  | 
|  318           </li>"""); |  | 
|  319       list.nodes.add(testItem); |  | 
|  320       testItem.query('#$_selectedIdPrefix$id').on.change.add((e) { |  | 
|  321         InputElement cb = testItem.query('#$_selectedIdPrefix$id'); |  | 
|  322         testCase.enabled = cb.checked; |  | 
|  323       }); |  | 
|  324       testItem.query('.test-label').on.click.add((e) { |  | 
|  325         var _testItem = document.query('#$_testIdPrefix$id'); |  | 
|  326         var _actions = _testItem.query('#$_actionIdPrefix$id'); |  | 
|  327         var _label = _testItem.query('.test-name'); |  | 
|  328         if (_actions.style.display == 'none') { |  | 
|  329           _actions.style.display = 'table'; |  | 
|  330           _label.classes.remove('closed'); |  | 
|  331           _label.classes.add('open'); |  | 
|  332         } else { |  | 
|  333           _actions.style.display = 'none'; |  | 
|  334           _label.classes.remove('open'); |  | 
|  335           _label.classes.add('closed'); |  | 
|  336         } |  | 
|  337       }); |  | 
|  338     } else { // Reset the test element. |  | 
|  339       testItem.classes.clear(); |  | 
|  340       testItem.classes.add('test-it'); |  | 
|  341       testItem.classes.add('status-pending'); |  | 
|  342       testItem.query('#$_actionIdPrefix$id').innerHTML = ''; |  | 
|  343     } |  | 
|  344   } |  | 
|  345  |  | 
|  346   // Actually test logging is handled by the slave, then posted |  | 
|  347   // back to the master. So here we know that the [message] argument |  | 
|  348   // is in the format used by [_Message]. |  | 
|  349   void logMessage(TestCase testCase, String message) { |  | 
|  350     var msg = new _Message.fromString(message); |  | 
|  351     if (msg.elapsed < 0) { // No associated test case. |  | 
|  352       document.query('#otherlogs').nodes.add( |  | 
|  353           new Element.html('<p>${msg.body}</p>')); |  | 
|  354     } else { |  | 
|  355       var actions = document.query('#$_testIdPrefix${testCase.id}'). |  | 
|  356           query('.test-actions'); |  | 
|  357       String elapsedText = msg.elapsed >= 0 ? "${msg.elapsed}ms" : ""; |  | 
|  358       actions.nodes.add(new Element.html( |  | 
|  359           "<li style='list-style-stype:none>" |  | 
|  360               "<div class='timer-result'>${elapsedText}</div>" |  | 
|  361               "<div class='test-title'>${msg.body}</div>" |  | 
|  362           "</li>")); |  | 
|  363     } |  | 
|  364   } |  | 
|  365  |  | 
|  366   void onTestResult(TestCase testCase) { |  | 
|  367     if (!testCase.enabled) return; |  | 
|  368     super.onTestResult(testCase); |  | 
|  369     if (testCase.message != '') { |  | 
|  370       logMessage(testCase, _Message.text(_Message.LOG, -1, testCase.message)); |  | 
|  371     } |  | 
|  372     int id = testCase.id; |  | 
|  373     var testItem = document.query('#$_testIdPrefix$id'); |  | 
|  374     var timeSpan = testItem.query('.test-timer-result'); |  | 
|  375     timeSpan.text = '${_testTime}ms'; |  | 
|  376     // Convert status into what we need for our CSS. |  | 
|  377     String result = 'status-error'; |  | 
|  378     if (testCase.result == 'pass') { |  | 
|  379       result = 'status-success'; |  | 
|  380     } else if (testCase.result == 'fail') { |  | 
|  381       result = 'status-failure'; |  | 
|  382     } |  | 
|  383     testItem.classes.remove('status-pending'); |  | 
|  384     testItem.classes.add(result); |  | 
|  385     // hide the actions |  | 
|  386     var actions = testItem.query('.test-actions'); |  | 
|  387     for (Element e in actions.nodes) { |  | 
|  388       e.classes.add(result); |  | 
|  389     } |  | 
|  390     actions.style.display = 'none'; |  | 
|  391   } |  | 
|  392  |  | 
|  393   void onDone(int passed, int failed, int errors, List<TestCase> results, |  | 
|  394       String uncaughtError) { |  | 
|  395     window.on.message.remove(_messageHandler); |  | 
|  396     window.on.error.remove(_onErrorClosure); |  | 
|  397     document.query('#busy').style.display = 'none'; |  | 
|  398     InputElement startButton = document.query('#start'); |  | 
|  399     startButton.disabled = false; |  | 
|  400   } |  | 
|  401 } |  | 
|  402  |  | 
|  403 /** |  | 
|  404  * Add the divs to the DOM if they are not present. We have a 'controls' |  | 
|  405  * div for control, 'specs' div with test results, a 'busy' div for the |  | 
|  406  * animated GIF used to indicate tests are running, and a 'slave' div to |  | 
|  407  * hold the iframe for the test. |  | 
|  408  */ |  | 
|  409 void _prepareDom() { |  | 
|  410   if (document.query('#control') == null) { |  | 
|  411     // Use this as an opportunity for adding the CSS too. |  | 
|  412     // I wanted to avoid having to include a css element explicitly |  | 
|  413     // in the main html file. I considered moving all the styles |  | 
|  414     // inline as attributes but that started getting very messy, |  | 
|  415     // so we do it this way. |  | 
|  416     document.body.nodes.add(new Element.html("<style>$_CSS</style>")); |  | 
|  417     document.body.nodes.add(new Element.html( |  | 
|  418         "<div id='control'>" |  | 
|  419             "<input id='start' disabled='true' type='button' value='Run'>" |  | 
|  420         "</div>")); |  | 
|  421     document.query('#start').on.click.add((e) { |  | 
|  422       InputElement startButton = document.query('#start'); |  | 
|  423       startButton.disabled = true; |  | 
|  424       rerunTests(); |  | 
|  425     }); |  | 
|  426   } |  | 
|  427   if (document.query('#otherlogs') == null) { |  | 
|  428     document.body.nodes.add(new Element.html( |  | 
|  429         "<div id='otherlogs'></div>")); |  | 
|  430   } |  | 
|  431   if (document.query('#specs') == null) { |  | 
|  432     document.body.nodes.add(new Element.html( |  | 
|  433         "<div id='specs'><div id='group-divs'></div></div>")); |  | 
|  434   } |  | 
|  435   if (document.query('#busy') == null) { |  | 
|  436     document.body.nodes.add(new Element.html( |  | 
|  437         "<div id='busy' style='display:none'><img src='googleballs.gif'>" |  | 
|  438         "</img></div>")); |  | 
|  439   } |  | 
|  440   if (document.query('#slave') == null) { |  | 
|  441     document.body.nodes.add(new Element.html("<div id='slave'></div>")); |  | 
|  442   } |  | 
|  443 } |  | 
|  444  |  | 
|  445 /** |  | 
|  446  * Allocate a Configuration. We allocate either a master or |  | 
|  447  * slave, depedning on whether the URL has a search part. |  | 
|  448  */ |  | 
|  449 void useInteractiveHtmlConfiguration() { |  | 
|  450   if (window.location.search == '') { // This is the master. |  | 
|  451     _prepareDom(); |  | 
|  452     configure(new MasterInteractiveHtmlConfiguration()); |  | 
|  453   } else { |  | 
|  454     configure(new SlaveInteractiveHtmlConfiguration()); |  | 
|  455   } |  | 
|  456 } |  | 
|  457  |  | 
|  458 String _CSS = """ |  | 
|  459 body { |  | 
|  460 font-family: Arial, sans-serif; |  | 
|  461 margin: 0; |  | 
|  462 font-size: 14px; |  | 
|  463 } |  | 
|  464  |  | 
|  465 #application h2, |  | 
|  466 #specs h2 { |  | 
|  467 margin: 0; |  | 
|  468 padding: 0.5em; |  | 
|  469 font-size: 1.1em; |  | 
|  470 } |  | 
|  471  |  | 
|  472 #header, |  | 
|  473 #application, |  | 
|  474 .test-info, |  | 
|  475 .test-actions li { |  | 
|  476 overflow: hidden; |  | 
|  477 } |  | 
|  478  |  | 
|  479 #application { |  | 
|  480 margin: 10px; |  | 
|  481 } |  | 
|  482  |  | 
|  483 #application iframe { |  | 
|  484 width: 100%; |  | 
|  485 height: 758px; |  | 
|  486 } |  | 
|  487  |  | 
|  488 #application iframe { |  | 
|  489 border: none; |  | 
|  490 } |  | 
|  491  |  | 
|  492 #specs { |  | 
|  493 padding-top: 50px |  | 
|  494 } |  | 
|  495  |  | 
|  496 .test-describe h2 { |  | 
|  497 border-top: 2px solid #BABAD1; |  | 
|  498 background-color: #efefef; |  | 
|  499 } |  | 
|  500  |  | 
|  501 .tests, |  | 
|  502 .test-it ol, |  | 
|  503 .status-display { |  | 
|  504 margin: 0; |  | 
|  505 padding: 0; |  | 
|  506 } |  | 
|  507  |  | 
|  508 .test-info { |  | 
|  509 margin-left: 1em; |  | 
|  510 margin-top: 0.5em; |  | 
|  511 border-radius: 8px 0 0 8px; |  | 
|  512 -webkit-border-radius: 8px 0 0 8px; |  | 
|  513 -moz-border-radius: 8px 0 0 8px; |  | 
|  514 cursor: pointer; |  | 
|  515 } |  | 
|  516  |  | 
|  517 .test-info:hover .test-name { |  | 
|  518 text-decoration: underline; |  | 
|  519 } |  | 
|  520  |  | 
|  521 .test-info .closed:before { |  | 
|  522 content: '\\25b8\\00A0'; |  | 
|  523 } |  | 
|  524  |  | 
|  525 .test-info .open:before { |  | 
|  526 content: '\\25be\\00A0'; |  | 
|  527 font-weight: bold; |  | 
|  528 } |  | 
|  529  |  | 
|  530 .test-it ol { |  | 
|  531 margin-left: 2.5em; |  | 
|  532 } |  | 
|  533  |  | 
|  534 .status-display, |  | 
|  535 .status-display li { |  | 
|  536 float: right; |  | 
|  537 } |  | 
|  538  |  | 
|  539 .status-display li { |  | 
|  540 padding: 5px 10px; |  | 
|  541 } |  | 
|  542  |  | 
|  543 .timer-result, |  | 
|  544 .test-title { |  | 
|  545 display: inline-block; |  | 
|  546 margin: 0; |  | 
|  547 padding: 4px; |  | 
|  548 } |  | 
|  549  |  | 
|  550 .test-actions .test-title, |  | 
|  551 .test-actions .test-result { |  | 
|  552 display: table-cell; |  | 
|  553 padding-left: 0.5em; |  | 
|  554 padding-right: 0.5em; |  | 
|  555 } |  | 
|  556  |  | 
|  557 .test-it { |  | 
|  558 list-style-type: none; |  | 
|  559 } |  | 
|  560  |  | 
|  561 .test-actions { |  | 
|  562 display: table; |  | 
|  563 } |  | 
|  564  |  | 
|  565 .test-actions li { |  | 
|  566 display: table-row; |  | 
|  567 } |  | 
|  568  |  | 
|  569 .timer-result { |  | 
|  570 width: 4em; |  | 
|  571 padding: 0 10px; |  | 
|  572 text-align: right; |  | 
|  573 font-family: monospace; |  | 
|  574 } |  | 
|  575  |  | 
|  576 .test-it pre, |  | 
|  577 .test-actions pre { |  | 
|  578 clear: left; |  | 
|  579 color: black; |  | 
|  580 margin-left: 6em; |  | 
|  581 } |  | 
|  582  |  | 
|  583 .test-describe { |  | 
|  584 margin: 5px 5px 10px 2em; |  | 
|  585 border-left: 1px solid #BABAD1; |  | 
|  586 border-right: 1px solid #BABAD1; |  | 
|  587 border-bottom: 1px solid #BABAD1; |  | 
|  588 padding-bottom: 0.5em; |  | 
|  589 } |  | 
|  590  |  | 
|  591 .test-actions .status-pending .test-title:before { |  | 
|  592 content: \\'\\\\00bb\\\\00A0\\'; |  | 
|  593 } |  | 
|  594  |  | 
|  595 .scrollpane { |  | 
|  596  max-height: 20em; |  | 
|  597  overflow: auto; |  | 
|  598 } |  | 
|  599  |  | 
|  600 #busy { |  | 
|  601 display: block; |  | 
|  602 } |  | 
|  603 /** Colors */ |  | 
|  604  |  | 
|  605 #header { |  | 
|  606 background-color: #F2C200; |  | 
|  607 } |  | 
|  608  |  | 
|  609 #application { |  | 
|  610 border: 1px solid #BABAD1; |  | 
|  611 } |  | 
|  612  |  | 
|  613 .status-pending .test-info { |  | 
|  614 background-color: #F9EEBC; |  | 
|  615 } |  | 
|  616  |  | 
|  617 .status-success .test-info { |  | 
|  618 background-color: #B1D7A1; |  | 
|  619 } |  | 
|  620  |  | 
|  621 .status-failure .test-info { |  | 
|  622 background-color: #FF8286; |  | 
|  623 } |  | 
|  624  |  | 
|  625 .status-error .test-info { |  | 
|  626 background-color: black; |  | 
|  627 color: white; |  | 
|  628 } |  | 
|  629  |  | 
|  630 .test-actions .status-success .test-title { |  | 
|  631 color: #30B30A; |  | 
|  632 } |  | 
|  633  |  | 
|  634 .test-actions .status-failure .test-title { |  | 
|  635 color: #DF0000; |  | 
|  636 } |  | 
|  637  |  | 
|  638 .test-actions .status-error .test-title { |  | 
|  639 color: black; |  | 
|  640 } |  | 
|  641  |  | 
|  642 .test-actions .timer-result { |  | 
|  643 color: #888; |  | 
|  644 } |  | 
|  645  |  | 
|  646 ul, menu, dir { |  | 
|  647 display: block; |  | 
|  648 list-style-type: disc; |  | 
|  649 -webkit-margin-before: 1em; |  | 
|  650 -webkit-margin-after: 1em; |  | 
|  651 -webkit-margin-start: 0px; |  | 
|  652 -webkit-margin-end: 0px; |  | 
|  653 -webkit-padding-start: 40px; |  | 
|  654 } |  | 
|  655  |  | 
|  656   """; |  | 
| OLD | NEW |