All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/8] Toaster improve test suite
@ 2016-02-23 17:31 Aníbal Limón
  2016-02-23 17:31 ` [PATCH 1/8] toaster/tests: Reorder/move current test code Aníbal Limón
                   ` (8 more replies)
  0 siblings, 9 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

This set of changes refactor/improve the current test suite now it can be
parameterized from command line.

The main goal for this set of patches are for integrate the toaster
test suite into AB. The AB part is already on master. [1] If this patchset
is merged the way for run Toaster test suite will change for use it read
README in this patchset.

Futher work are related to improve/fix the current test suite also i added
a TODO list.

Also the changes can be found at,

http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=alimon/toaster_tests_squash

[1] http://git.yoctoproject.org/cgit/cgit.cgi/yocto-autobuilder/commit/?id=4ad04739d7414046775be84eeb2553d12acf94ef

Aníbal Limón (8):
  toaster/tests: Reorder/move current test code.
  toaster/tests: Add __init__.py and base.py.
  toaster/tests: Improve ui.py module.
  toaster/tests: Adds setup test case.
  toaster/tests: Add helpers.py module.
  toaster-test: Refactor and imporvements.
  toaster/tests: Add README, TODO and requeriments.
  toaster/tests: Adds Copyright information.

 bin/toaster-test                                   |  238 ++
 .../contrib/tts/toasteruitest/run_toastertests.py  |  155 --
 .../tts/toasteruitest/toaster_automation_test.py   | 2376 -------------------
 .../contrib/tts/toasteruitest/toaster_test.cfg     |   25 -
 lib/toaster/tests/README                           |   64 +
 lib/toaster/tests/TODO                             |   13 +
 lib/toaster/tests/__init__.py                      |    0
 lib/toaster/tests/base.py                          |   28 +
 lib/toaster/tests/helpers.py                       |  192 ++
 lib/toaster/tests/setup.py                         |   94 +
 lib/toaster/tests/toaster_test.cfg                 |   25 +
 lib/toaster/tests/ui.py                            | 2379 ++++++++++++++++++++
 toaster-tests-requirements.txt                     |    5 +
 13 files changed, 3038 insertions(+), 2556 deletions(-)
 create mode 100755 bin/toaster-test
 delete mode 100755 lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
 delete mode 100755 lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
 delete mode 100644 lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
 create mode 100644 lib/toaster/tests/README
 create mode 100644 lib/toaster/tests/TODO
 create mode 100644 lib/toaster/tests/__init__.py
 create mode 100644 lib/toaster/tests/base.py
 create mode 100755 lib/toaster/tests/helpers.py
 create mode 100644 lib/toaster/tests/setup.py
 create mode 100644 lib/toaster/tests/toaster_test.cfg
 create mode 100644 lib/toaster/tests/ui.py
 create mode 100644 toaster-tests-requirements.txt

-- 
2.1.4



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/8] toaster/tests: Reorder/move current test code.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 2/8] toaster/tests: Add __init__.py and base.py Aníbal Limón
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

In order to provide a better structure of the toaster test
suite:

bin/toaster-test: Main script for run toaster tests.
lib/toaster/tests: Contains ui test code and configuration.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 bin/toaster-test                                   |  155 ++
 .../contrib/tts/toasteruitest/run_toastertests.py  |  155 --
 .../tts/toasteruitest/toaster_automation_test.py   | 2376 --------------------
 .../contrib/tts/toasteruitest/toaster_test.cfg     |   25 -
 lib/toaster/tests/toaster_test.cfg                 |   25 +
 lib/toaster/tests/ui.py                            | 2376 ++++++++++++++++++++
 6 files changed, 2556 insertions(+), 2556 deletions(-)
 create mode 100755 bin/toaster-test
 delete mode 100755 lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
 delete mode 100755 lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
 delete mode 100644 lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
 create mode 100644 lib/toaster/tests/toaster_test.cfg
 create mode 100644 lib/toaster/tests/ui.py

diff --git a/bin/toaster-test b/bin/toaster-test
new file mode 100755
index 0000000..2b312cb
--- /dev/null
+++ b/bin/toaster-test
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+
+# Copyright
+
+# DESCRIPTION
+# This is script for running all selected toaster cases on
+# selected web browsers manifested in toaster_test.cfg.
+
+# 1. How to start toaster in yocto:
+# $ source poky/oe-init-build-env
+# $ source toaster start
+# $ bitbake core-image-minimal
+
+# 2. How to install selenium on Ubuntu:
+# $ sudo apt-get install scrot python-pip
+# $ sudo pip install selenium
+
+# 3. How to install selenium addon in firefox:
+# Download the lastest firefox addon from http://release.seleniumhq.org/selenium-ide/
+# Then install it. You can also install firebug and firepath addon
+
+# 4. How to start writing a new case:
+# All you need to do is to implement the function test_xxx() and  pile it on.
+
+# 5. How to test with Chrome browser
+# Download/install chrome on host
+# Download chromedriver from https://code.google.com/p/chromedriver/downloads/list  according to your host type
+# put chromedriver in PATH, (e.g. /usr/bin/, bear in mind to chmod)
+# For windows host, you may put chromedriver.exe in the same directory as chrome.exe
+
+import unittest, sys, os, platform
+import ConfigParser
+import argparse
+from toaster_automation_test import toaster_cases
+
+
+def get_args_parser():
+    description = "Script that runs toaster auto tests."
+    parser = argparse.ArgumentParser(description=description)
+    parser.add_argument('--run-all-tests', required=False, action="store_true", dest="run_all_tests", default=False,
+                       help='Run all tests.')
+    parser.add_argument('--run-suite', required=False, dest='run_suite', default=False,
+                       help='run suite (defined in cfg file)')
+
+    return parser
+
+
+def get_tests():
+    testslist = []
+
+    prefix = 'toaster_automation_test.toaster_cases'
+
+    for t in dir(toaster_cases):
+        if t.startswith('test_'):
+            testslist.append('.'.join((prefix, t)))
+
+    return testslist
+
+
+def get_tests_from_cfg(suite=None):
+
+    testslist = []
+    config = ConfigParser.SafeConfigParser()
+    config.read('toaster_test.cfg')
+
+    if suite is not None:
+        target_suite = suite.lower()
+
+        # TODO: if suite is valid suite
+
+    else:
+        target_suite = platform.system().lower()
+
+    try:
+        tests_from_cfg = eval(config.get('toaster_test_' + target_suite, 'test_cases'))
+    except:
+        print 'Failed to get test cases from cfg file. Make sure the format is correct.'
+        return None
+
+    prefix = 'toaster_automation_test.toaster_cases.test_'
+    for t in tests_from_cfg:
+        testslist.append(prefix + str(t))
+
+    return testslist
+
+def main():
+
+    # In case this script is called from other directory
+    os.chdir(os.path.abspath(sys.path[0]))
+
+    parser = get_args_parser()
+    args = parser.parse_args()
+
+    if args.run_all_tests:
+        testslist = get_tests()
+    elif args.run_suite:
+        testslist = get_tests_from_cfg(args.run_suite)
+        os.environ['TOASTER_SUITE'] = args.run_suite
+    else:
+        testslist = get_tests_from_cfg()
+
+    if not testslist:
+        print 'Failed to get test cases.'
+        exit(1)
+
+    suite = unittest.TestSuite()
+    loader = unittest.TestLoader()
+    loader.sortTestMethodsUsing = None
+    runner = unittest.TextTestRunner(verbosity=2, resultclass=buildResultClass(args))
+
+    for test in testslist:
+        try:
+            suite.addTests(loader.loadTestsFromName(test))
+        except:
+            return 1
+
+    result = runner.run(suite)
+
+    if result.wasSuccessful():
+        return 0
+    else:
+        return 1
+
+
+def buildResultClass(args):
+    """Build a Result Class to use in the testcase execution"""
+
+    class StampedResult(unittest.TextTestResult):
+        """
+        Custom TestResult that prints the time when a test starts.  As toaster-auto
+        can take a long time (ie a few hours) to run, timestamps help us understand
+        what tests are taking a long time to execute.
+        """
+        def startTest(self, test):
+            import time
+            self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
+            super(StampedResult, self).startTest(test)
+
+    return StampedResult
+
+
+if __name__ == "__main__":
+
+    try:
+        ret = main()
+    except:
+        ret = 1
+        import traceback
+        traceback.print_exc(5)
+    finally:
+        if os.getenv('TOASTER_SUITE'):
+            del os.environ['TOASTER_SUITE']
+    sys.exit(ret)
+
+
diff --git a/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py b/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
deleted file mode 100755
index 2b312cb..0000000
--- a/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/python
-
-# Copyright
-
-# DESCRIPTION
-# This is script for running all selected toaster cases on
-# selected web browsers manifested in toaster_test.cfg.
-
-# 1. How to start toaster in yocto:
-# $ source poky/oe-init-build-env
-# $ source toaster start
-# $ bitbake core-image-minimal
-
-# 2. How to install selenium on Ubuntu:
-# $ sudo apt-get install scrot python-pip
-# $ sudo pip install selenium
-
-# 3. How to install selenium addon in firefox:
-# Download the lastest firefox addon from http://release.seleniumhq.org/selenium-ide/
-# Then install it. You can also install firebug and firepath addon
-
-# 4. How to start writing a new case:
-# All you need to do is to implement the function test_xxx() and  pile it on.
-
-# 5. How to test with Chrome browser
-# Download/install chrome on host
-# Download chromedriver from https://code.google.com/p/chromedriver/downloads/list  according to your host type
-# put chromedriver in PATH, (e.g. /usr/bin/, bear in mind to chmod)
-# For windows host, you may put chromedriver.exe in the same directory as chrome.exe
-
-import unittest, sys, os, platform
-import ConfigParser
-import argparse
-from toaster_automation_test import toaster_cases
-
-
-def get_args_parser():
-    description = "Script that runs toaster auto tests."
-    parser = argparse.ArgumentParser(description=description)
-    parser.add_argument('--run-all-tests', required=False, action="store_true", dest="run_all_tests", default=False,
-                       help='Run all tests.')
-    parser.add_argument('--run-suite', required=False, dest='run_suite', default=False,
-                       help='run suite (defined in cfg file)')
-
-    return parser
-
-
-def get_tests():
-    testslist = []
-
-    prefix = 'toaster_automation_test.toaster_cases'
-
-    for t in dir(toaster_cases):
-        if t.startswith('test_'):
-            testslist.append('.'.join((prefix, t)))
-
-    return testslist
-
-
-def get_tests_from_cfg(suite=None):
-
-    testslist = []
-    config = ConfigParser.SafeConfigParser()
-    config.read('toaster_test.cfg')
-
-    if suite is not None:
-        target_suite = suite.lower()
-
-        # TODO: if suite is valid suite
-
-    else:
-        target_suite = platform.system().lower()
-
-    try:
-        tests_from_cfg = eval(config.get('toaster_test_' + target_suite, 'test_cases'))
-    except:
-        print 'Failed to get test cases from cfg file. Make sure the format is correct.'
-        return None
-
-    prefix = 'toaster_automation_test.toaster_cases.test_'
-    for t in tests_from_cfg:
-        testslist.append(prefix + str(t))
-
-    return testslist
-
-def main():
-
-    # In case this script is called from other directory
-    os.chdir(os.path.abspath(sys.path[0]))
-
-    parser = get_args_parser()
-    args = parser.parse_args()
-
-    if args.run_all_tests:
-        testslist = get_tests()
-    elif args.run_suite:
-        testslist = get_tests_from_cfg(args.run_suite)
-        os.environ['TOASTER_SUITE'] = args.run_suite
-    else:
-        testslist = get_tests_from_cfg()
-
-    if not testslist:
-        print 'Failed to get test cases.'
-        exit(1)
-
-    suite = unittest.TestSuite()
-    loader = unittest.TestLoader()
-    loader.sortTestMethodsUsing = None
-    runner = unittest.TextTestRunner(verbosity=2, resultclass=buildResultClass(args))
-
-    for test in testslist:
-        try:
-            suite.addTests(loader.loadTestsFromName(test))
-        except:
-            return 1
-
-    result = runner.run(suite)
-
-    if result.wasSuccessful():
-        return 0
-    else:
-        return 1
-
-
-def buildResultClass(args):
-    """Build a Result Class to use in the testcase execution"""
-
-    class StampedResult(unittest.TextTestResult):
-        """
-        Custom TestResult that prints the time when a test starts.  As toaster-auto
-        can take a long time (ie a few hours) to run, timestamps help us understand
-        what tests are taking a long time to execute.
-        """
-        def startTest(self, test):
-            import time
-            self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
-            super(StampedResult, self).startTest(test)
-
-    return StampedResult
-
-
-if __name__ == "__main__":
-
-    try:
-        ret = main()
-    except:
-        ret = 1
-        import traceback
-        traceback.print_exc(5)
-    finally:
-        if os.getenv('TOASTER_SUITE'):
-            del os.environ['TOASTER_SUITE']
-    sys.exit(ret)
-
-
diff --git a/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py b/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
deleted file mode 100755
index d8f838a..0000000
--- a/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
+++ /dev/null
@@ -1,2376 +0,0 @@
-#!/usr/bin/python
-# Copyright
-
-# DESCRIPTION
-# This is toaster automation base class and test cases file
-
-# History:
-# 2015.03.09  inital version
-# 2015.03.23  adding toaster_test.cfg, run_toastertest.py so we can run case by case from outside
-
-# Briefs:
-# This file is comprised of 3 parts:
-# I:   common utils like sorting, getting attribute.. etc
-# II:  base class part, which complies with unittest frame work and
-#      contains class selenium-based functions
-# III: test cases
-#      to add new case: just implement new test_xxx() function in class toaster_cases
-
-# NOTES for cases:
-# case 946:
-# step 6 - 8 needs to be observed using screenshots
-# case 956:
-# step 2 - 3 needs to be run manually
-
-import unittest, time, re, sys, getopt, os, logging, string, errno, exceptions
-import shutil, argparse, ConfigParser, platform, json
-from selenium import webdriver
-from selenium.common.exceptions import NoSuchElementException
-from selenium import selenium
-from selenium.webdriver.common.by import By
-from selenium.webdriver.common.keys import Keys
-from selenium.webdriver.support.ui import Select
-import sqlite3 as sqlite
-
-
-###########################################
-#                                         #
-# PART I: utils stuff                     #
-#                                         #
-###########################################
-
-class Listattr(object):
-    """
-    Set of list attribute. This is used to determine what the list content is.
-    Later on we may add more attributes here.
-    """
-    NULL = "null"
-    NUMBERS = "numbers"
-    STRINGS = "strings"
-    PERCENT = "percentage"
-    SIZE = "size"
-    UNKNOWN = "unknown"
-
-
-def get_log_root_dir():
-    max_depth = 5
-    parent_dir = '../'
-    for number in range(0, max_depth):
-        if os.path.isdir(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log'):
-            log_root_dir = os.path.abspath(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log')
-            break
-
-    if number == (max_depth - 1):
-        print 'No log dir found. Please check'
-        raise Exception
-
-    return log_root_dir
-
-
-def mkdir_p(dir):
-    try:
-        os.makedirs(dir)
-    except OSError as exc:
-        if exc.errno == errno.EEXIST and os.path.isdir(dir):
-            pass
-        else:
-            raise
-
-
-def get_list_attr(testlist):
-    """
-    To determine the list content
-    """
-    if not testlist:
-        return Listattr.NULL
-    listtest = testlist[:]
-    try:
-        listtest.remove('')
-    except ValueError:
-        pass
-    pattern_percent = re.compile(r"^([0-9])+(\.)?([0-9])*%$")
-    pattern_size = re.compile(r"^([0-9])+(\.)?([0-9])*( )*(K)*(M)*(G)*B$")
-    pattern_number = re.compile(r"^([0-9])+(\.)?([0-9])*$")
-    def get_patterned_number(pattern, tlist):
-        count = 0
-        for item in tlist:
-            if re.search(pattern, item):
-                count += 1
-        return count
-    if get_patterned_number(pattern_percent, listtest) == len(listtest):
-        return Listattr.PERCENT
-    elif get_patterned_number(pattern_size, listtest) == len(listtest):
-        return Listattr.SIZE
-    elif get_patterned_number(pattern_number, listtest) == len(listtest):
-        return Listattr.NUMBERS
-    else:
-        return Listattr.STRINGS
-
-
-def is_list_sequenced(testlist):
-    """
-    Function to tell if list is sequenced
-    Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
-    Each has respective way to determine if it's sequenced.
-    """
-    test_list = testlist[:]
-    try:
-        test_list.remove('')
-    except ValueError:
-        pass
-
-    if get_list_attr(testlist) == Listattr.NULL :
-        return True
-
-    elif get_list_attr(testlist) == Listattr.STRINGS :
-        return (sorted(test_list) == test_list)
-
-    elif get_list_attr(testlist) == Listattr.NUMBERS :
-        list_number = []
-        for item in test_list:
-            list_number.append(eval(item))
-        return (sorted(list_number) == list_number)
-
-    elif get_list_attr(testlist) == Listattr.PERCENT :
-        list_number = []
-        for item in test_list:
-            list_number.append(eval(item.strip('%')))
-        return (sorted(list_number) == list_number)
-
-    elif get_list_attr(testlist) == Listattr.SIZE :
-        list_number = []
-        # currently SIZE is splitted by space
-        for item in test_list:
-            if item.split()[1].upper() == "KB":
-                list_number.append(1024 * eval(item.split()[0]))
-            elif item.split()[1].upper() == "MB":
-                list_number.append(1024 * 1024 * eval(item.split()[0]))
-            elif item.split()[1].upper() == "GB":
-                list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
-            else:
-                list_number.append(eval(item.split()[0]))
-        return (sorted(list_number) == list_number)
-
-    else:
-        print 'Unrecognized list type, please check'
-        return False
-
-
-def is_list_inverted(testlist):
-    """
-    Function to tell if list is inverted
-    Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
-    Each has respective way to determine if it's inverted.
-    """
-    test_list = testlist[:]
-    try:
-        test_list.remove('')
-    except ValueError:
-        pass
-
-    if get_list_attr(testlist) == Listattr.NULL :
-        return True
-
-    elif get_list_attr(testlist) == Listattr.STRINGS :
-        return (sorted(test_list, reverse = True) == test_list)
-
-    elif get_list_attr(testlist) == Listattr.NUMBERS :
-        list_number = []
-        for item in test_list:
-            list_number.append(eval(item))
-        return (sorted(list_number, reverse = True) == list_number)
-
-    elif get_list_attr(testlist) == Listattr.PERCENT :
-        list_number = []
-        for item in test_list:
-            list_number.append(eval(item.strip('%')))
-        return (sorted(list_number, reverse = True) == list_number)
-
-    elif get_list_attr(testlist) == Listattr.SIZE :
-        list_number = []
-        # currently SIZE is splitted by space. such as 0 B; 1 KB; 2 MB
-        for item in test_list:
-            if item.split()[1].upper() == "KB":
-                list_number.append(1024 * eval(item.split()[0]))
-            elif item.split()[1].upper() == "MB":
-                list_number.append(1024 * 1024 * eval(item.split()[0]))
-            elif item.split()[1].upper() == "GB":
-                list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
-            else:
-                list_number.append(eval(item.split()[0]))
-        return (sorted(list_number, reverse = True) == list_number)
-
-    else:
-        print 'Unrecognized list type, please check'
-        return False
-
-def replace_file_content(filename, item, option):
-    f = open(filename)
-    lines = f.readlines()
-    f.close()
-    output = open(filename, 'w')
-    for line in lines:
-        if line.startswith(item):
-            output.write(item + " = '" + option + "'\n")
-        else:
-            output.write(line)
-    output.close()
-
-def extract_number_from_string(s):
-    """
-    extract the numbers in a string. return type is 'list'
-    """
-    return re.findall(r'([0-9]+)', s)
-
-# Below is decorator derived from toaster backend test code
-class NoParsingFilter(logging.Filter):
-    def filter(self, record):
-        return record.levelno == 100
-
-def LogResults(original_class):
-    orig_method = original_class.run
-
-    from time import strftime, gmtime
-    caller = 'toaster'
-    timestamp = strftime('%Y%m%d%H%M%S',gmtime())
-    logfile = os.path.join(os.getcwd(),'results-'+caller+'.'+timestamp+'.log')
-    linkfile = os.path.join(os.getcwd(),'results-'+caller+'.log')
-
-    #rewrite the run method of unittest.TestCase to add testcase logging
-    def run(self, result, *args, **kws):
-        orig_method(self, result, *args, **kws)
-        passed = True
-        testMethod = getattr(self, self._testMethodName)
-        #if test case is decorated then use it's number, else use it's name
-        try:
-            test_case = testMethod.test_case
-        except AttributeError:
-            test_case = self._testMethodName
-
-        class_name = str(testMethod.im_class).split("'")[1]
-
-        #create custom logging level for filtering.
-        custom_log_level = 100
-        logging.addLevelName(custom_log_level, 'RESULTS')
-
-        def results(self, message, *args, **kws):
-            if self.isEnabledFor(custom_log_level):
-                self.log(custom_log_level, message, *args, **kws)
-        logging.Logger.results = results
-
-        logging.basicConfig(filename=logfile,
-                            filemode='w',
-                            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
-                            datefmt='%H:%M:%S',
-                            level=custom_log_level)
-        for handler in logging.root.handlers:
-            handler.addFilter(NoParsingFilter())
-        local_log = logging.getLogger(caller)
-
-        #check status of tests and record it
-
-        for (name, msg) in result.errors:
-            if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
-                local_log.results("Testcase "+str(test_case)+": ERROR")
-                local_log.results("Testcase "+str(test_case)+":\n"+msg)
-                passed = False
-        for (name, msg) in result.failures:
-            if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
-                local_log.results("Testcase "+str(test_case)+": FAILED")
-                local_log.results("Testcase "+str(test_case)+":\n"+msg)
-                passed = False
-        for (name, msg) in result.skipped:
-            if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
-                local_log.results("Testcase "+str(test_case)+": SKIPPED")
-                passed = False
-        if passed:
-            local_log.results("Testcase "+str(test_case)+": PASSED")
-
-        # Create symlink to the current log
-        if os.path.exists(linkfile):
-            os.remove(linkfile)
-        os.symlink(logfile, linkfile)
-
-    original_class.run = run
-
-    return original_class
-
-
-###########################################
-#                                         #
-# PART II: base class                     #
-#                                         #
-###########################################
-
-@LogResults
-class toaster_cases_base(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.log = cls.logger_create()
-
-    def setUp(self):
-        self.screenshot_sequence = 1
-        self.verificationErrors = []
-        self.accept_next_alert = True
-        self.host_os = platform.system().lower()
-        if os.getenv('TOASTER_SUITE'):
-            self.target_suite = os.getenv('TOASTER_SUITE')
-        else:
-            self.target_suite = self.host_os
-
-        self.parser = ConfigParser.SafeConfigParser()
-        self.parser.read('toaster_test.cfg')
-        self.base_url = eval(self.parser.get('toaster_test_' + self.target_suite, 'toaster_url'))
-
-        # create log dir . Currently , we put log files in log/tmp. After all
-        # test cases are done, move them to log/$datetime dir
-        self.log_tmp_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + 'tmp'
-        try:
-            mkdir_p(self.log_tmp_dir)
-        except OSError :
-            logging.error("%(asctime)s Cannot create tmp dir under log, please check your privilege")
-        # self.log = self.logger_create()
-        # driver setup
-        self.setup_browser()
-
-    @staticmethod
-    def logger_create():
-        log_file = "toaster-auto-" + time.strftime("%Y%m%d%H%M%S") + ".log"
-        if os.path.exists("toaster-auto.log"): os.remove("toaster-auto.log")
-        os.symlink(log_file, "toaster-auto.log")
-
-        log = logging.getLogger("toaster")
-        log.setLevel(logging.DEBUG)
-
-        fh = logging.FileHandler(filename=log_file, mode='w')
-        fh.setLevel(logging.DEBUG)
-
-        ch = logging.StreamHandler(sys.stdout)
-        ch.setLevel(logging.INFO)
-
-        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-        fh.setFormatter(formatter)
-        ch.setFormatter(formatter)
-
-        log.addHandler(fh)
-        log.addHandler(ch)
-
-        return log
-
-
-    def setup_browser(self, *browser_path):
-        self.browser = eval(self.parser.get('toaster_test_' + self.target_suite, 'test_browser'))
-        print self.browser
-        if self.browser == "firefox":
-            driver = webdriver.Firefox()
-        elif self.browser == "chrome":
-            driver = webdriver.Chrome()
-        elif self.browser == "ie":
-            driver = webdriver.Ie()
-        else:
-            driver = None
-            print "unrecognized browser type, please check"
-        self.driver = driver
-        self.driver.implicitly_wait(30)
-        return self.driver
-
-
-    def save_screenshot(self,  **log_args):
-        """
-        This function is used to save screen either by os interface or selenium interface.
-        How to use:
-        self.save_screenshot(screenshot_type = 'native'/'selenium', log_sub_dir = 'xxx',
-                             append_name = 'stepx')
-        where native means screenshot func provided by OS,
-        selenium means screenshot func provided by selenium webdriver
-        """
-        types = [log_args.get('screenshot_type')]
-        # when no screenshot_type is specified
-        if types == [None]:
-            types = ['native', 'selenium']
-        # normally append_name is used to specify which step..
-        add_name = log_args.get('append_name')
-        if not add_name:
-            add_name = '-'
-        # normally there's no need to specify sub_dir
-        sub_dir = log_args.get('log_sub_dir')
-        if not sub_dir:
-            # use casexxx as sub_dir name
-            sub_dir = 'case' + str(self.case_no)
-        for item in types:
-            log_dir = self.log_tmp_dir + os.sep + sub_dir
-            mkdir_p(log_dir)
-            log_path = log_dir + os.sep +  self.browser + '-' +\
-                    item + '-' + add_name + '-' + str(self.screenshot_sequence) + '.png'
-            if item == 'native':
-                if self.host_os == "linux":
-                    os.system("scrot " + log_path)
-                elif self.host_os=="darwin":
-                    os.system("screencapture -x " + log_path)
-            elif item == 'selenium':
-                self.driver.get_screenshot_as_file(log_path)
-            self.screenshot_sequence += 1
-
-    def browser_delay(self):
-        """
-        currently this is a workaround for some chrome test.
-        Sometimes we need a delay to accomplish some operation.
-        But for firefox, mostly we don't need this.
-        To be discussed
-        """
-        if self.browser == "chrome":
-            time.sleep(1)
-        return
-
-
-# these functions are not contained in WebDriver class..
-    def find_element_by_text(self, string):
-        return self.driver.find_element_by_xpath("//*[text()='" + string + "']")
-
-
-    def find_elements_by_text(self, string):
-        return self.driver.find_elements_by_xpath("//*[text()='" + string + "']")
-
-
-    def find_element_by_text_in_table(self, table_id, text_string):
-        """
-        This is used to search some certain 'text' in certain table
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element = table_element.find_element_by_xpath("//*[text()='" + text_string + "']")
-        except NoSuchElementException, e:
-            print 'no element found'
-            raise
-        return element
-
-
-    def find_element_by_link_text_in_table(self, table_id, link_text):
-        """
-        Assume there're multiple suitable "find_element_by_link_text".
-        In this circumstance we need to specify "table".
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element = table_element.find_element_by_link_text(link_text)
-        except NoSuchElementException, e:
-            print 'no element found'
-            raise
-        return element
-
-
-    def find_elements_by_link_text_in_table(self, table_id, link_text):
-        """
-        Search link-text in certain table. This helps to narrow down search area.
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element_list = table_element.find_elements_by_link_text(link_text)
-        except NoSuchElementException, e:
-            print 'no element found'
-            raise
-        return element_list
-
-
-    def find_element_by_partial_link_text_in_table(self, table_id, link_text):
-        """
-        Search element by partial link text in certain table.
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element = table_element.find_element_by_partial_link_text(link_text)
-            return element
-        except NoSuchElementException, e:
-            print 'no element found'
-            raise
-
-
-    def find_elements_by_partial_link_text_in_table(self, table_id, link_text):
-        """
-        Assume there're multiple suitable "find_partial_element_by_link_text".
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element_list = table_element.find_elements_by_partial_link_text(link_text)
-            return element_list
-        except NoSuchElementException, e:
-            print 'no element found'
-            raise
-
-
-    def find_element_by_xpath_in_table(self, table_id, xpath):
-        """
-        This helps to narrow down search area. Especially useful when dealing with pop-up form.
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element = table_element.find_element_by_xpath(xpath)
-        except NoSuchElementException, e:
-            print 'no element found'
-            raise
-        return element
-
-
-    def find_elements_by_xpath_in_table(self, table_id, xpath):
-        """
-        This helps to narrow down search area. Especially useful when dealing with pop-up form.
-        """
-        try:
-            table_element = self.get_table_element(table_id)
-            element_list = table_element.find_elements_by_xpath(xpath)
-        except NoSuchElementException, e:
-            print 'no elements found'
-            raise
-        return element_list
-
-
-    def shortest_xpath(self, pname, pvalue):
-        return "//*[@" + pname + "='" + pvalue + "']"
-
-
-#usually elements in the same column are with same class name. for instance: class="outcome" .TBD
-    def get_table_column_text(self, attr_name, attr_value):
-        c_xpath = self.shortest_xpath(attr_name, attr_value)
-        elements = self.driver.find_elements_by_xpath(c_xpath)
-        c_list = []
-        for element in elements:
-            c_list.append(element.text)
-        return c_list
-
-
-    def get_table_column_text_by_column_number(self, table_id, column_number):
-        c_xpath = "//*[@id='" + table_id + "']//td[" + str(column_number) + "]"
-        elements = self.driver.find_elements_by_xpath(c_xpath)
-        c_list = []
-        for element in elements:
-            c_list.append(element.text)
-        return c_list
-
-
-    def get_table_head_text(self, *table_id):
-#now table_id is a tuple...
-        if table_id:
-            thead_xpath = "//*[@id='" + table_id[0] + "']//thead//th[text()]"
-            elements = self.driver.find_elements_by_xpath(thead_xpath)
-            c_list = []
-            for element in elements:
-                if element.text:
-                    c_list.append(element.text)
-            return c_list
-#default table on page
-        else:
-            return self.driver.find_element_by_xpath("//*/table/thead").text
-
-
-
-    def get_table_element(self, table_id, *coordinate):
-        if len(coordinate) == 0:
-#return whole-table element
-            element_xpath = "//*[@id='" + table_id + "']"
-            try:
-                element = self.driver.find_element_by_xpath(element_xpath)
-            except NoSuchElementException, e:
-                raise
-            return element
-        row = coordinate[0]
-
-        if len(coordinate) == 1:
-#return whole-row element
-            element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]"
-            try:
-                element = self.driver.find_element_by_xpath(element_xpath)
-            except NoSuchElementException, e:
-                return False
-            return element
-#now we are looking for an element with specified X and Y
-        column = coordinate[1]
-
-        element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]"
-        try:
-            element = self.driver.find_element_by_xpath(element_xpath)
-        except NoSuchElementException, e:
-            return False
-        return element
-
-
-    def get_table_data(self, table_id, row_count, column_count):
-        row = 1
-        Lists = []
-        while row <= row_count:
-            column = 1
-            row_content=[]
-            while column <= column_count:
-                s= "//*[@id='" + table_id + "']/tbody/tr[" + str(row) +"]/td[" + str(column) + "]"
-                v = self.driver.find_element_by_xpath(s).text
-                row_content.append(v)
-                column = column + 1
-                print("row_content=",row_content)
-            Lists.extend(row_content)
-            print Lists[row-1][0]
-            row = row + 1
-        return Lists
-
-    # The is_xxx_present functions only returns True/False
-    # All the log work is done in test procedure, so we can easily trace back
-    # using logging
-    def is_text_present (self, patterns):
-        for pattern in patterns:
-            if str(pattern) not in self.driver.page_source:
-                print 'Text "'+pattern+'" is missing'
-                return False
-        return True
-
-
-    def is_element_present(self, how, what):
-        try:
-            self.driver.find_element(how, what)
-        except NoSuchElementException, e:
-            print 'Could not find element '+str(what)+' by ' + str(how)
-            return False
-        return True
-
-
-    def is_alert_present(self):
-        try: self.driver.switch_to_alert()
-        except NoAlertPresentException, e: return False
-        return True
-
-
-    def close_alert_and_get_its_text(self):
-        try:
-            alert = self.driver.switch_to_alert()
-            alert_text = alert.text
-            if self.accept_next_alert:
-                alert.accept()
-            else:
-                alert.dismiss()
-            return alert_text
-        finally: self.accept_next_alert = True
-
-
-    def get_case_number(self):
-        """
-        what case are we running now
-        """
-        funcname = sys._getframe(1).f_code.co_name
-        caseno_str = funcname.strip('test_')
-        try:
-            caseno = int(caseno_str)
-        except ValueError:
-            print "get case number error! please check if func name is test_xxx"
-            return False
-        return caseno
-
-
-    def tearDown(self):
-        self.log.info(' END: CASE %s log \n\n' % str(self.case_no))
-        self.driver.quit()
-        self.assertEqual([], self.verificationErrors)
-
-
-###################################################################
-#                                                                 #
-# PART III: test cases                                            #
-# please refer to                                                 #
-# https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=xxx  #
-#                                                                 #
-###################################################################
-
-# Note: to comply with the unittest framework, we call these test_xxx functions
-# from run_toastercases.py to avoid calling setUp() and tearDown() multiple times
-
-
-class toaster_cases(toaster_cases_base):
-        ##############
-        #  CASE 901  #
-        ##############
-    def test_901(self):
-        # the reason why get_case_number is not in setUp function is that
-        # otherwise it returns "setUp" instead of "test_xxx"
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # open all columns
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # adding explicitly wait for chromedriver..-_-
-        self.browser_delay()
-        self.driver.find_element_by_id("started_on").click()
-        self.browser_delay()
-        self.driver.find_element_by_id("time").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # dict: {lint text name : actual class name}
-        table_head_dict = {'Outcome':'outcome', 'Recipe':'target', 'Machine':'machine', 'Started on':'started_on', 'Completed on':'completed_on', \
-                'Errors':'errors_no', 'Warnings':'warnings_no', 'Time':'time'}
-        for key in table_head_dict:
-            try:
-                self.driver.find_element_by_link_text(key).click()
-            except Exception, e:
-                self.log.error("%s cannot be found on page" % key)
-                raise
-            column_list = self.get_table_column_text("class", table_head_dict[key])
-            # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
-            # the point is, after another click, it should be another order
-            if is_list_inverted(column_list):
-                self.driver.find_element_by_link_text(key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
-            else:
-                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not sequenced" % key))
-                self.driver.find_element_by_link_text(key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
-        self.log.info("case passed")
-
-
-        ##############
-        #  CASE 902  #
-        ##############
-    def test_902(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # Could add more test patterns here in the future. Also, could search some items other than target column in future..
-        patterns = ["minimal", "sato"]
-        for pattern in patterns:
-            ori_target_column_texts = self.get_table_column_text("class", "target")
-            print ori_target_column_texts
-            self.driver.find_element_by_id("search").clear()
-            self.driver.find_element_by_id("search").send_keys(pattern)
-            self.driver.find_element_by_id("search-button").click()
-            new_target_column_texts = self.get_table_column_text("class", "target")
-            # if nothing found, we still count it as "pass"
-            if new_target_column_texts:
-                for text in new_target_column_texts:
-                    self.assertTrue(text.find(pattern), msg=("%s item doesn't exist " % pattern))
-            self.driver.find_element_by_css_selector("i.icon-remove").click()
-            target_column_texts = self.get_table_column_text("class", "target")
-            self.assertTrue(ori_target_column_texts == target_column_texts, msg=("builds changed after operations"))
-
-
-        ##############
-        #  CASE 903  #
-        ##############
-    def test_903(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # when opening a new page, "started_on" is not displayed by default
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # currently all the delay are for chrome driver -_-
-        self.browser_delay()
-        self.driver.find_element_by_id("started_on").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # step 4
-        items = ["Outcome", "Completed on", "Started on"]
-        for item in items:
-            try:
-                temp_element = self.find_element_by_text_in_table('otable', item)
-                # this is how we find "filter icon" in the same level as temp_element(where "a" means clickable, "i" means icon)
-                self.assertTrue(temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']"))
-            except Exception,e:
-                self.assertFalse(True, msg=(" %s cannot be found! %s" % (item, e)))
-                raise
-        # step 5-6
-        temp_element = self.find_element_by_link_text_in_table('otable', 'Outcome')
-        temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
-        self.browser_delay()
-        # the 2nd option, whatever it is
-        self.driver.find_element_by_xpath("(//input[@name='filter'])[2]").click()
-        # click "Apply" button
-        self.driver.find_element_by_xpath("//*[@id='filter_outcome']//*[text()='Apply']").click()
-        # save screen here
-        time.sleep(1)
-        self.save_screenshot(screenshot_type='selenium', append_name='step5')
-        temp_element = self.find_element_by_link_text_in_table('otable', 'Completed on')
-        temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
-        self.browser_delay()
-        self.driver.find_element_by_xpath("//*[@id='filter_completed_on']//*[text()='Apply']").click()
-        # save screen here to compare to previous one
-        # please note that for chrome driver, need a little break before saving
-        # screen here -_-
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step6')
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("core-image")
-        self.driver.find_element_by_id("search-button").click()
-
-
-        ##############
-        #  CASE 904  #
-        ##############
-    def test_904(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_partial_link_text("core-image").click()
-        self.driver.find_element_by_link_text("Tasks").click()
-        self.table_name = 'otable'
-        # This is how we find the "default" rows-number!
-        rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
-        print rows_displayed
-        self.assertTrue(self.get_table_element(self.table_name, rows_displayed), msg=("not enough rows displayed"))
-        self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1), \
-                         msg=("more rows displayed than expected"))
-        # Search text box background text is "Search tasks"
-        self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search tasks']"),\
-                        msg=("background text doesn't exist"))
-
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("busybox")
-        self.driver.find_element_by_id("search-button").click()
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step5')
-        self.driver.find_element_by_css_selector("i.icon-remove").click()
-        # Save screen here
-        self.save_screenshot(screenshot_type='selenium', append_name='step5_2')
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("cpu_used").click()
-        self.driver.find_element_by_id("disk_io").click()
-        self.driver.find_element_by_id("recipe_version").click()
-        self.driver.find_element_by_id("time_taken").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # The operation is the same as case901
-        # dict: {lint text name : actual class name}
-        table_head_dict = {'Order':'order', 'Recipe':'recipe_name', 'Task':'task_name', 'Executed':'executed', \
-                           'Outcome':'outcome', 'Cache attempt':'cache_attempt', 'Time (secs)':'time_taken', 'CPU usage':'cpu_used', \
-                           'Disk I/O (ms)':'disk_io'}
-        for key in table_head_dict:
-        # This is tricky here: we are doing so because there may be more than 1
-        # same-name link_text in one page. So we only find element inside the table
-            self.find_element_by_link_text_in_table(self.table_name, key).click()
-            column_list = self.get_table_column_text("class", table_head_dict[key])
-        # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
-        # the point is, after another click, it should be another order
-        # the first case is special:this means every item in column_list is the same, so
-        # after one click, either sequenced or inverted will be fine
-            if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
-                or (not column_list) :
-                self.find_element_by_link_text_in_table(self.table_name, key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list), \
-                                msg=("%s column not in any order" % key))
-            elif is_list_inverted(column_list):
-                self.find_element_by_link_text_in_table(self.table_name, key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
-            else:
-                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
-                self.find_element_by_link_text_in_table(self.table_name, key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
-        # step 8-10
-        # filter dict: {link text name : filter table name in xpath}
-        filter_dict = {'Executed':'filter_executed', 'Outcome':'filter_outcome', 'Cache attempt':'filter_cache_attempt'}
-        for key in filter_dict:
-            temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
-            # find the filter icon besides it.
-            # And here we must have break (1 sec) to get the popup stuff
-            temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
-            self.browser_delay()
-            avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
-            for number in range(0, len(avail_options)):
-                avail_options[number].click()
-                self.browser_delay()
-                # click "Apply"
-                self.driver.find_element_by_xpath("//*[@id='" + filter_dict[key]  + "']//*[@type='submit']").click()
-                # insert screen capture here
-                self.browser_delay()
-                self.save_screenshot(screenshot_type='selenium', append_name='step8')
-                # after the last option was clicked, we don't need operation below anymore
-                if number < len(avail_options)-1:
-                     try:
-                        temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
-                        temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
-                        avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
-                     except:
-                        print "in exception"
-                        self.find_element_by_text("Show all tasks").click()
-#                        self.driver.find_element_by_xpath("//*[@id='searchform']/button[2]").click()
-                        temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
-                        temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
-                        avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
-                     self.browser_delay()
-        # step 11
-        for item in ['order', 'task_name', 'executed', 'outcome', 'recipe_name', 'recipe_version']:
-            try:
-                self.find_element_by_xpath_in_table(self.table_name, "./tbody/tr[1]/*[@class='" + item + "']/a").click()
-            except NoSuchElementException, e:
-            # let it go...
-                print 'no item in the colum' + item
-            # insert screen shot here
-            self.save_screenshot(screenshot_type='selenium', append_name='step11')
-            self.driver.back()
-        # step 12-14
-        # about test_dict: please refer to testcase 904 requirement step 12-14
-        test_dict = {
-            'Time':{
-                'class':'time_taken',
-                'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Time (secs)'],
-                'check_column_list':['cpu_used', 'cache_attempt', 'disk_io', 'order', 'recipe_version']
-            },
-            'CPU usage':{
-                'class':'cpu_used',
-                'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'CPU usage'],
-                'check_column_list':['cache_attempt', 'disk_io', 'order', 'recipe_version', 'time_taken']
-            },
-            'Disk I/O':{
-                'class':'disk_io',
-                'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Disk I/O (ms)'],
-                'check_column_list':['cpu_used', 'cache_attempt', 'order', 'recipe_version', 'time_taken']
-            }
-        }
-        for key in test_dict:
-            self.find_element_by_partial_link_text_in_table('nav', 'core-image').click()
-            self.find_element_by_link_text_in_table('nav', key).click()
-            head_list = self.get_table_head_text('otable')
-            for item in test_dict[key]['check_head_list']:
-                self.assertTrue(item in head_list, msg=("%s not in head row" % item))
-            column_list = self.get_table_column_text('class', test_dict[key]['class'])
-            self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
-
-            self.driver.find_element_by_id("edit-columns-button").click()
-            for item2 in test_dict[key]['check_column_list']:
-                self.driver.find_element_by_id(item2).click()
-            self.driver.find_element_by_id("edit-columns-button").click()
-            # TBD: save screen here
-
-
-        ##############
-        #  CASE 906  #
-        ##############
-    def test_906(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'Packages').click()
-        # find "bash" in first column (Packages)
-        self.driver.find_element_by_xpath("//*[@id='otable']//td[1]//*[text()='bash']").click()
-        # save sceen here to observe...
-        # step 6
-        self.driver.find_element_by_partial_link_text("Generated files").click()
-        head_list = self.get_table_head_text('otable')
-        for item in ['File', 'Size']:
-            self.assertTrue(item in head_list, msg=("%s not in head row" % item))
-        c_list = self.get_table_column_text('class', 'path')
-        self.assertTrue(is_list_sequenced(c_list), msg=("column not in order"))
-        # step 7
-        self.driver.find_element_by_partial_link_text("Runtime dependencies").click()
-        # save sceen here to observe...
-        # note that here table name is not 'otable'
-        head_list = self.get_table_head_text('dependencies')
-        for item in ['Package', 'Version', 'Size']:
-            self.assertTrue(item in head_list, msg=("%s not in head row" % item))
-        c_list = self.get_table_column_text_by_column_number('dependencies', 1)
-        self.assertTrue(is_list_sequenced(c_list), msg=("list not in order"))
-        texts = ['Size', 'License', 'Recipe', 'Recipe version', 'Layer', \
-                     'Layer commit']
-        self.failUnless(self.is_text_present(texts))
-
-
-        ##############
-        #  CASE 910  #
-        ##############
-    def test_910(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        image_type="core-image-minimal"
-        test_package1="busybox"
-        test_package2="lib"
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text(image_type).click()
-        self.driver.find_element_by_link_text("Recipes").click()
-        self.save_screenshot(screenshot_type='selenium', append_name='step3')
-
-        self.table_name = 'otable'
-        # This is how we find the "default" rows-number!
-        rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
-        print rows_displayed
-        self.assertTrue(self.get_table_element(self.table_name, rows_displayed))
-        self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1))
-
-        # Check the default table is sorted by Recipe
-        tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
-        print tasks_column_count
-        default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
-        #print default_column_list
-
-        self.assertTrue(is_list_sequenced(default_column_list))
-
-        # Search text box background text is "Search recipes"
-        self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
-
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys(test_package1)
-        self.driver.find_element_by_id("search-button").click()
-        # Save screen here
-        self.save_screenshot(screenshot_type='selenium', append_name='step4')
-        self.driver.find_element_by_css_selector("i.icon-remove").click()
-        self.save_screenshot(screenshot_type='selenium', append_name='step4_2')
-
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("depends_on").click()
-        self.driver.find_element_by_id("layer_version__branch").click()
-        self.driver.find_element_by_id("layer_version__layer__commit").click()
-        self.driver.find_element_by_id("depends_by").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-
-        self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
-        # Check the inverted table by Recipe
-        # Recipe doesn't have class name
-        #inverted_tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
-        #print inverted_tasks_column_count
-        #inverted_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
-        #print inverted_column_list
-
-        #self.driver.find_element_by_partial_link_text("zlib").click()
-        #self.driver.back()
-        #self.assertTrue(is_list_inverted(inverted_column_list))
-        #self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
-
-        table_head_dict = {'Recipe':'recipe__name', 'Recipe file':'recipe_file', 'Section':'recipe_section', \
-                'License':'recipe_license', 'Layer':'layer_version__layer__name', \
-                'Layer branch':'layer_version__branch'}
-        for key in table_head_dict:
-            self.find_element_by_link_text_in_table(self.table_name, key).click()
-            column_list = self.get_table_column_text("class", table_head_dict[key])
-            if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
-                    or (not column_list) :
-                self.find_element_by_link_text_in_table(self.table_name, key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list))
-                self.driver.find_element_by_partial_link_text("acl").click()
-                self.driver.back()
-                self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list))
-                # Search text box background text is "Search recipes"
-                self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
-                self.driver.find_element_by_id("search").clear()
-                self.driver.find_element_by_id("search").send_keys(test_package2)
-                self.driver.find_element_by_id("search-button").click()
-                column_search_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_search_list) or is_list_inverted(column_search_list))
-                self.driver.find_element_by_css_selector("i.icon-remove").click()
-            elif is_list_inverted(column_list):
-                self.find_element_by_link_text_in_table(self.table_name, key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_list))
-                self.driver.find_element_by_partial_link_text("acl").click()
-                self.driver.back()
-                self.assertTrue(is_list_sequenced(column_list))
-                # Search text box background text is "Search recipes"
-                self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
-                self.driver.find_element_by_id("search").clear()
-                self.driver.find_element_by_id("search").send_keys(test_package2)
-                self.driver.find_element_by_id("search-button").click()
-                column_search_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_sequenced(column_search_list))
-                self.driver.find_element_by_css_selector("i.icon-remove").click()
-            else:
-                self.assertTrue(is_list_sequenced(column_list),  msg=("list %s not sequenced" % key))
-                self.find_element_by_link_text_in_table(self.table_name, key).click()
-                column_list = self.get_table_column_text("class", table_head_dict[key])
-                self.assertTrue(is_list_inverted(column_list))
-                try:
-                    self.driver.find_element_by_partial_link_text("acl").click()
-                except:
-                    self.driver.find_element_by_partial_link_text("zlib").click()
-                self.driver.back()
-                self.assertTrue(is_list_inverted(column_list))
-                # Search text box background text is "Search recipes"
-                self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
-                self.driver.find_element_by_id("search").clear()
-                self.driver.find_element_by_id("search").send_keys(test_package2)
-                self.driver.find_element_by_id("search-button").click()
-                column_search_list = self.get_table_column_text("class", table_head_dict[key])
-                #print column_search_list
-                self.assertTrue(is_list_inverted(column_search_list))
-                self.driver.find_element_by_css_selector("i.icon-remove").click()
-
-        # Bug 5919
-        for key in table_head_dict:
-            print key
-            self.find_element_by_link_text_in_table(self.table_name, key).click()
-            self.driver.find_element_by_id("edit-columns-button").click()
-            self.driver.find_element_by_id(table_head_dict[key]).click()
-            self.driver.find_element_by_id("edit-columns-button").click()
-            self.browser_delay()
-            # After hide the column, the default table should be sorted by Recipe
-            tasks_column_count = len(self.driver.find_elements_by_partial_link_text("acl"))
-            #print tasks_column_count
-            default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
-            #print default_column_list
-            self.assertTrue(is_list_sequenced(default_column_list))
-
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("recipe_file").click()
-        self.driver.find_element_by_id("recipe_section").click()
-        self.driver.find_element_by_id("recipe_license").click()
-        self.driver.find_element_by_id("layer_version__layer__name").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-
-
-        ##############
-        #  CASE 911  #
-        ##############
-    def test_911(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'Recipes').click()
-        # step 3-5
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("lib")
-        self.driver.find_element_by_id("search-button").click()
-        # save screen here for observation
-        self.save_screenshot(screenshot_type='selenium', append_name='step5')
-        # step 6
-        self.driver.find_element_by_css_selector("i.icon-remove").click()
-        self.driver.find_element_by_id("search").clear()
-        # we deliberately want "no result" here
-        self.driver.find_element_by_id("search").send_keys("no such input")
-        self.driver.find_element_by_id("search-button").click()
-        try:
-            self.find_element_by_text("Show all recipes").click()
-        except:
-            self.fail(msg='Could not identify blank page elements')
-
-        ##############
-        #  CASE 912  #
-        ##############
-    def test_912(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'Recipes').click()
-        # step 3
-        head_list = self.get_table_head_text('otable')
-        for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
-            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("depends_on").click()
-        self.driver.find_element_by_id("layer_version__branch").click()
-        self.driver.find_element_by_id("layer_version__layer__commit").click()
-        self.driver.find_element_by_id("depends_by").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # check if columns selected above is shown
-        check_list = ['Dependencies', 'Layer branch', 'Layer commit', 'Reverse dependencies']
-        head_list = self.get_table_head_text('otable')
-        time.sleep(2)
-        print head_list
-        for item in check_list:
-            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
-        # un-check 'em all
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("depends_on").click()
-        self.driver.find_element_by_id("layer_version__branch").click()
-        self.driver.find_element_by_id("layer_version__layer__commit").click()
-        self.driver.find_element_by_id("depends_by").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # don't exist any more
-        head_list = self.get_table_head_text('otable')
-        for item in check_list:
-            self.assertFalse(item in head_list, msg=("item %s should not be in head row" % item))
-
-
-        ##############
-        #  CASE 913  #
-        ##############
-    def test_913(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'Recipes').click()
-        # step 3
-        head_list = self.get_table_head_text('otable')
-        for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
-            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
-        # step 4
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # save screen
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step4')
-        self.driver.find_element_by_id("edit-columns-button").click()
-
-
-        ##############
-        #  CASE 914  #
-        ##############
-    def test_914(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        image_type="core-image-minimal"
-        test_package1="busybox"
-        test_package2="gdbm"
-        test_package3="gettext-native"
-        driver = self.driver
-        driver.maximize_window()
-        driver.get(self.base_url)
-        driver.find_element_by_link_text(image_type).click()
-        driver.find_element_by_link_text("Recipes").click()
-        driver.find_element_by_link_text(test_package1).click()
-
-        self.table_name = 'information'
-
-        tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr/td[1]"))
-        tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr[1]/td"))
-        print 'rows: '+str(tasks_row_count)
-        print 'columns: '+str(tasks_column_count)
-
-        Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
-        print ("Tasks_column=", Tasks_column)
-
-        key_tasks=["do_fetch", "do_unpack",  "do_patch", "do_configure", "do_compile", "do_install", "do_package", "do_build"]
-        i = 0
-        while i < len(key_tasks):
-            if key_tasks[i] not in Tasks_column:
-                print ("Error! Missing key task: %s" % key_tasks[i])
-            else:
-                print ("%s is in tasks" % key_tasks[i])
-            i = i + 1
-
-        if Tasks_column.index(key_tasks[0]) != 0:
-            print ("Error! %s is not in the right position" % key_tasks[0])
-        else:
-            print ("%s is in right position" % key_tasks[0])
-
-        if Tasks_column[-1] != key_tasks[-1]:
-            print ("Error! %s is not in the right position" % key_tasks[-1])
-        else:
-            print ("%s is in right position" % key_tasks[-1])
-
-        driver.find_element_by_partial_link_text("Packages (").click()
-        packages_name = driver.find_element_by_partial_link_text("Packages (").text
-        print packages_name
-        packages_num = int(filter(str.isdigit, repr(packages_name)))
-        print packages_num
-
-        #switch the table to show more than 10 rows at a time
-        self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").click()
-        Select(driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select")).select_by_value('150')
-        self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").send_keys(Keys.ENTER)
-
-        packages_row_count = len(driver.find_elements_by_xpath("//*[@id='otable']/tbody/tr/td[1]"))
-        print packages_row_count
-
-        if packages_num != packages_row_count:
-            print ("Error! The packages number is not correct")
-        else:
-            print ("The packages number is correct")
-
-        driver.find_element_by_partial_link_text("Build dependencies (").click()
-        depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
-        print depends_name
-        depends_num = int(filter(str.isdigit, repr(depends_name)))
-        print depends_num
-
-        if depends_num == 0:
-            depends_message = repr(driver.find_element_by_css_selector("div.alert.alert-info").text)
-            print depends_message
-            if depends_message.find("has no build dependencies.") < 0:
-                print ("Error! The message isn't expected.")
-            else:
-                print ("The message is expected")
-        else:
-            depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]"))
-            print depends_row_count
-            if depends_num != depends_row_count:
-                print ("Error! The dependent packages number is not correct")
-            else:
-                print ("The dependent packages number is correct")
-
-        driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
-        rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
-        print rdepends_name
-        rdepends_num = int(filter(str.isdigit, repr(rdepends_name)))
-        print rdepends_num
-
-        if rdepends_num == 0:
-            rdepends_message = repr(driver.find_element_by_css_selector("#brought-in-by > div.alert.alert-info").text)
-            print rdepends_message
-            if rdepends_message.find("has no reverse build dependencies.") < 0:
-                print ("Error! The message isn't expected.")
-            else:
-                print ("The message is expected")
-        else:
-            print ("The reverse dependent packages number is correct")
-
-        driver.find_element_by_link_text("Recipes").click()
-        driver.find_element_by_link_text(test_package2).click()
-        driver.find_element_by_partial_link_text("Packages (").click()
-        driver.find_element_by_partial_link_text("Build dependencies (").click()
-        driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
-
-
-        driver.find_element_by_link_text("Recipes").click()
-        driver.find_element_by_link_text(test_package3).click()
-
-        native_tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr/td[1]"))
-        native_tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr[1]/td"))
-        print native_tasks_row_count
-        print native_tasks_column_count
-
-        Native_Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
-        print ("Native_Tasks_column=", Native_Tasks_column)
-
-        native_key_tasks=["do_fetch", "do_unpack",  "do_patch", "do_configure", "do_compile", "do_install", "do_build"]
-        i = 0
-        while i < len(native_key_tasks):
-            if native_key_tasks[i] not in Native_Tasks_column:
-                print ("Error! Missing key task: %s" % native_key_tasks[i])
-            else:
-                print ("%s is in tasks" % native_key_tasks[i])
-            i = i + 1
-
-        if Native_Tasks_column.index(native_key_tasks[0]) != 0:
-            print ("Error! %s is not in the right position" % native_key_tasks[0])
-        else:
-            print ("%s is in right position" % native_key_tasks[0])
-
-        if Native_Tasks_column[-1] != native_key_tasks[-1]:
-            print ("Error! %s is not in the right position" % native_key_tasks[-1])
-        else:
-            print ("%s is in right position" % native_key_tasks[-1])
-
-        driver.find_element_by_partial_link_text("Packages (").click()
-        native_packages_name = driver.find_element_by_partial_link_text("Packages (").text
-        print native_packages_name
-        native_packages_num = int(filter(str.isdigit, repr(native_packages_name)))
-        print native_packages_num
-
-        if native_packages_num != 0:
-            print ("Error! Native task shouldn't have any packages.")
-        else:
-            native_package_message = repr(driver.find_element_by_css_selector("#packages-built > div.alert.alert-info").text)
-            print native_package_message
-            if native_package_message.find("does not build any packages.") < 0:
-                print ("Error! The message for native task isn't expected.")
-            else:
-                print ("The message for native task is expected.")
-
-        driver.find_element_by_partial_link_text("Build dependencies (").click()
-        native_depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
-        print native_depends_name
-        native_depends_num = int(filter(str.isdigit, repr(native_depends_name)))
-        print native_depends_num
-
-        native_depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]"))
-        print native_depends_row_count
-
-        if native_depends_num != native_depends_row_count:
-            print ("Error! The dependent packages number is not correct")
-        else:
-            print ("The dependent packages number is correct")
-
-        driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
-        native_rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
-        print native_rdepends_name
-        native_rdepends_num = int(filter(str.isdigit, repr(native_rdepends_name)))
-        print native_rdepends_num
-
-        native_rdepends_row_count = len(driver.find_elements_by_xpath("//*[@id='brought-in-by']/table/tbody/tr/td[1]"))
-        print native_rdepends_row_count
-
-        if native_rdepends_num != native_rdepends_row_count:
-            print ("Error! The reverse dependent packages number is not correct")
-        else:
-            print ("The reverse dependent packages number is correct")
-
-        driver.find_element_by_link_text("Recipes").click()
-
-
-        ##############
-        #  CASE 915  #
-        ##############
-    def test_915(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        # step 3
-        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
-        self.driver.find_element_by_link_text("BitBake variables").click()
-        # step 4
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("lib")
-        self.driver.find_element_by_id("search-button").click()
-        # save screen to see result
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step4')
-        # step 5
-        self.driver.find_element_by_css_selector("i.icon-remove").click()
-        head_list = self.get_table_head_text('otable')
-        print head_list
-        print len(head_list)
-        self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
-                        msg=("head row contents wrong"))
-        # step 8
-        # search other string. and click "Variable" to re-sort, check if table
-        # head is still the same
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("poky")
-        self.driver.find_element_by_id("search-button").click()
-        self.find_element_by_link_text_in_table('otable', 'Variable').click()
-        head_list = self.get_table_head_text('otable')
-        self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
-                        msg=("head row contents wrong"))
-        self.find_element_by_link_text_in_table('otable', 'Variable').click()
-        head_list = self.get_table_head_text('otable')
-        self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
-                        msg=("head row contents wrong"))
-
-
-        ##############
-        #  CASE 916  #
-        ##############
-    def test_916(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        # step 2-3
-        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
-        self.driver.find_element_by_link_text("BitBake variables").click()
-        variable_list = self.get_table_column_text('class', 'variable_name')
-        self.assertTrue(is_list_sequenced(variable_list), msg=("list not in order"))
-        # step 4
-        self.find_element_by_link_text_in_table('otable', 'Variable').click()
-        variable_list = self.get_table_column_text('class', 'variable_name')
-        self.assertTrue(is_list_inverted(variable_list), msg=("list not inverted"))
-        self.find_element_by_link_text_in_table('otable', 'Variable').click()
-        # step 5
-        # searching won't change the sequentiality
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("lib")
-        self.driver.find_element_by_id("search-button").click()
-        variable_list = self.get_table_column_text('class', 'variable_name')
-        self.assertTrue(is_list_sequenced(variable_list), msg=("list not in order"))
-
-
-        ##############
-        #  CASE 923  #
-        ##############
-    def test_923(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # Step 2
-        # default sequence in "Completed on" column is inverted
-        c_list = self.get_table_column_text('class', 'completed_on')
-        self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
-        # step 3
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("started_on").click()
-        self.driver.find_element_by_id("time").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        head_list = self.get_table_head_text('otable')
-        for item in ['Outcome', 'Recipe', 'Machine', 'Started on', 'Completed on', 'Failed tasks', 'Errors', 'Warnings', 'Time', "Image files", "Project"]:
-            self.failUnless(item in head_list, msg=item+' is missing from table head.')
-
-
-        ##############
-        #  CASE 924  #
-        ##############
-    def test_924(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # Please refer to case 924 requirement
-        # default sequence in "Completed on" column is inverted
-        c_list = self.get_table_column_text('class', 'completed_on')
-        self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
-        # Step 4
-        # click Errors , order in "Completed on" should be disturbed. Then hide
-        # error column to check if order in "Completed on" can be restored
-#THIS TEST IS NO LONGER VALID DUE TO DESIGN CHANGES. LEAVING IN PENDING UPDATES TO DESIGN
-        #self.find_element_by_link_text_in_table('otable', 'Errors').click()
-        #self.driver.find_element_by_id("edit-columns-button").click()
-        #self.driver.find_element_by_id("errors_no").click()
-        #self.driver.find_element_by_id("edit-columns-button").click()
-        # Note: without time.sleep here, there'll be unpredictable error..TBD
-        time.sleep(1)
-        c_list = self.get_table_column_text('class', 'completed_on')
-        self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
-
-
-        ##############
-        #  CASE 940  #
-        ##############
-    def test_940(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        # Step 2-3
-        self.find_element_by_link_text_in_table('nav', 'Packages').click()
-        check_head_list = ['Package', 'Package version', 'Size', 'Recipe']
-        head_list = self.get_table_head_text('otable')
-        self.assertTrue(head_list == check_head_list, msg=("head row not as expected"))
-        # Step 4
-        # pulldown menu
-        option_ids = ['recipe__layer_version__layer__name', 'recipe__layer_version__branch', \
-                      'recipe__layer_version__layer__commit', 'license', 'recipe__version']
-        self.driver.find_element_by_id("edit-columns-button").click()
-        for item in option_ids:
-            if not self.driver.find_element_by_id(item).is_selected():
-                self.driver.find_element_by_id(item).click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # save screen here to observe that 'Package' and 'Package version' is
-        # not selectable
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step4')
-
-
-        ##############
-        #  CASE 941  #
-        ##############
-    def test_941(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        # Step 2-3
-        self.find_element_by_link_text_in_table('nav', 'Packages').click()
-        # column -- Package
-        column_list = self.get_table_column_text_by_column_number('otable', 1)
-        self.assertTrue(is_list_sequenced(column_list), msg=("list not in order"))
-        self.find_element_by_link_text_in_table('otable', 'Size').click()
-
-
-        ##############
-        #  CASE 942  #
-        ##############
-    def test_942(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.driver.find_element_by_link_text("Packages").click()
-        #get initial table header
-        head_list = self.get_table_head_text('otable')
-        #remove the Recipe column from table header
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("recipe__name").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        #get modified table header
-        new_head = self.get_table_head_text('otable')
-        self.assertTrue(head_list > new_head)
-
-        ##############
-        #  CASE 943  #
-        ##############
-    def test_943(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.driver.find_element_by_link_text("Packages").click()
-        #search for the "bash" package -> this should definitely be present
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("bash")
-        self.driver.find_element_by_id("search-button").click()
-        #check for the search result message "XX packages found"
-        self.assertTrue(self.is_text_present("packages found"), msg=("no packages found text"))
-
-
-        ##############
-        #  CASE 944  #
-        ##############
-    def test_944(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        # step 1: test Recipes page stuff
-        self.driver.find_element_by_link_text("Recipes").click()
-        # for these 3 items, default status is not-checked
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("layer_version__branch").click()
-        self.driver.find_element_by_id("layer_version__layer__commit").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # otable is the recipes table here
-        otable_head_text = self.get_table_head_text('otable')
-        for item in ["Layer", "Layer branch", "Layer commit"]:
-            self.failIf(item not in otable_head_text, msg=item+' not in table head.')
-        # click the fist recipe, whatever it is
-        self.get_table_element("otable", 1, 1).click()
-        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit", "Recipe file"]), \
-                        msg=("text not in web page"))
-
-        # step 2: test Packages page stuff. almost same as above
-        self.driver.back()
-        self.browser_delay()
-        self.driver.find_element_by_link_text("Packages").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("recipe__layer_version__layer__name").click()
-        self.driver.find_element_by_id("recipe__layer_version__branch").click()
-        self.driver.find_element_by_id("recipe__layer_version__layer__commit").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        otable_head_text = self.get_table_head_text("otable")
-        for item in ["Layer", "Layer branch", "Layer commit"]:
-            self.assertFalse(item not in otable_head_text, msg=("item %s should be in head row" % item))
-        # click the fist recipe, whatever it is
-        self.get_table_element("otable", 1, 1).click()
-        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
-                        msg=("text not in web page"))
-
-        # step 3: test Packages core-image-minimal(images) stuff. almost same as above. Note when future element-id changes...
-        self.driver.back()
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        self.driver.find_element_by_id("layer_name").click()
-        self.driver.find_element_by_id("layer_branch").click()
-        self.driver.find_element_by_id("layer_commit").click()
-        self.driver.find_element_by_id("edit-columns-button").click()
-        otable_head_text = self.get_table_head_text("otable")
-        for item in ["Layer", "Layer branch", "Layer commit"]:
-            self.assertFalse(item not in otable_head_text, msg=("item %s should be in head row" % item))
-        # click the fist recipe, whatever it is
-        self.get_table_element("otable", 1, 1).click()
-        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
-                        msg=("text not in web page"))
-
-        # step 4: check Configuration page
-        self.driver.back()
-        self.driver.find_element_by_link_text("Configuration").click()
-        otable_head_text = self.get_table_head_text()
-        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
-                        msg=("text not in web page"))
-
-
-        ##############
-        #  CASE 945  #
-        ##############
-    def test_945(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        for item in ["Packages", "Recipes", "Tasks"]:
-            self.driver.get(self.base_url)
-            self.driver.find_element_by_link_text("core-image-minimal").click()
-            self.driver.find_element_by_link_text(items).click()
-
-            # this may be page specific. If future page content changes, try to replace it with new xpath
-            xpath_showrows = "/html/body/div[4]/div/div/div[2]/div[2]/div[2]/div/div/div[2]/select"
-            xpath_table = "html/body/div[4]/div/div/div[2]/div[2]/table/tbody"#"id=('otable')/tbody"
-            self.driver.find_element_by_xpath(xpath_showrows).click()
-            rows_displayed = int(self.driver.find_element_by_xpath(xpath_showrows + "/option[2]").text)
-
-            # not sure if this is a Selenium Select bug: If page is not refreshed here, "select(by visible text)" operation will go back to 100-row page
-            # Sure we can use driver.get(url) to refresh page, but since page will vary, we use click link text here
-            self.driver.find_element_by_link_text(items).click()
-            Select(self.driver.find_element_by_css_selector("select.pagesize")).select_by_visible_text(str(rows_displayed))
-            self.failUnless(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed) +"]"))
-            self.failIf(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed+1) +"]"))
-
-            # click 1st package, then go back to check if it's still those rows shown.
-            self.driver.find_element_by_xpath(xpath_otable + "/tr[1]/td[1]/a").click()
-            time.sleep(3)
-            self.driver.find_element_by_link_text(item).click()
-            self.assertTrue(self.is_element_present(By.XPATH, xpath_otable + "/tr[" + str(option_tobeselected) +"]"),\
-                            msg=("Row %d should exist" %option_tobeselected))
-            self.assertFalse(self.is_element_present(By.XPATH, xpath_otable + "/tr[" + str(option_tobeselected+1) +"]"),\
-                            msg=("Row %d should not exist" %(option_tobeselected+1)))
-
-
-
-        ##############
-        #  CASE 946  #
-        ##############
-    def test_946(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.driver.find_element_by_link_text("Configuration").click()
-        # step 3-4
-        check_list = ["Summary", "BitBake variables"]
-        for item in check_list:
-            if not self.is_element_present(how=By.LINK_TEXT, what=item):
-                self.log.error("%s not found" %item)
-        if not self.is_text_present(['Layers', 'Layer', 'Layer branch', 'Layer commit']):
-            self.log.error("text not found")
-        # step 5
-        self.driver.find_element_by_link_text("BitBake variables").click()
-        if not self.is_text_present(['Variable', 'Value', 'Set in file', 'Description']):
-            self.log.error("text not found")
-        # This may be unstable because it's page-specific
-        # step 6: this is how we find filter beside "Set in file"
-        temp_element = self.find_element_by_text_in_table('otable', "Set in file")
-        temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
-        self.browser_delay()
-        self.driver.find_element_by_xpath("(//input[@name='filter'])[3]").click()
-        btns = self.driver.find_elements_by_css_selector("button.btn.btn-primary")
-        for btn in btns:
-            try:
-                btn.click()
-                break
-            except:
-                pass
-        # save screen here
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step6')
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # save screen here
-        # step 7
-        # we should manually check the step 6-8 result using screenshot
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step7')
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # step 9
-        # click the 1st item, no matter what it is
-        self.driver.find_element_by_xpath("//*[@id='otable']/tbody/tr[1]/td[1]/a").click()
-        # give it 1 sec so the pop-up becomes the "active_element"
-        time.sleep(1)
-        element = self.driver.switch_to.active_element
-        check_list = ['Order', 'Configuration file', 'Operation', 'Line number']
-        for item in check_list:
-            if item not in element.text:
-                self.log.error("%s not found" %item)
-        # any better way to close this pop-up? ... TBD
-        element.find_element_by_class_name("close").click()
-        # step 10 : need to manually check "Yocto Manual" in saved screen
-        self.driver.find_element_by_css_selector("i.icon-share.get-info").click()
-        # save screen here
-        time.sleep(5)
-        self.save_screenshot(screenshot_type='native', append_name='step10')
-
-
-        ##############
-        #  CASE 947  #
-        ##############
-    def test_947(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
-        # step 2
-        self.driver.find_element_by_link_text("BitBake variables").click()
-        # step 3
-        def xpath_option(column_name):
-            # return xpath of options under "Edit columns" button
-            return self.shortest_xpath('id', 'navTab') + self.shortest_xpath('id', 'editcol') \
-                + self.shortest_xpath('id', column_name)
-        self.driver.find_element_by_id('edit-columns-button').click()
-        # by default, option "Description" and "Set in file" were checked
-        self.driver.find_element_by_xpath(xpath_option('description')).click()
-        self.driver.find_element_by_xpath(xpath_option('file')).click()
-        self.driver.find_element_by_id('edit-columns-button').click()
-        check_list = ['Description', 'Set in file']
-        head_list = self.get_table_head_text('otable')
-        for item in check_list:
-            self.assertFalse(item in head_list, msg=("item %s should not be in head row" % item))
-        # check these 2 options and verify again
-        self.driver.find_element_by_id('edit-columns-button').click()
-        self.driver.find_element_by_xpath(xpath_option('description')).click()
-        self.driver.find_element_by_xpath(xpath_option('file')).click()
-        self.driver.find_element_by_id('edit-columns-button').click()
-        head_list = self.get_table_head_text('otable')
-        for item in check_list:
-            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
-
-
-        ##############
-        #  CASE 948  #
-        ##############
-    def test_948(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
-        self.driver.find_element_by_link_text("BitBake variables").click()
-        #get number of variables visible by default
-        number_before_search = self.driver.find_element_by_class_name('page-header').text
-        # search for a while...
-        self.driver.find_element_by_id("search").clear()
-        self.driver.find_element_by_id("search").send_keys("BB")
-        self.driver.find_element_by_id("search-button").click()
-        #get number of variables visible after search
-        number_after_search = self.driver.find_element_by_class_name('page-header').text
-        self.assertTrue(number_before_search > number_after_search, msg=("items should be less after search"))
-
-
-        ##############
-        #  CASE 949  #
-        ##############
-    def test_949(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        self.find_element_by_link_text_in_table('nav', 'core-image-minimal').click()
-        # step 3
-        try:
-            self.driver.find_element_by_partial_link_text("Packages included")
-            self.driver.find_element_by_partial_link_text("Directory structure")
-        except Exception,e:
-            self.log.error(e)
-            self.assertFalse(True)
-        # step 4
-        head_list = self.get_table_head_text('otable')
-        for item in ['Package', 'Package version', 'Size', 'Dependencies', 'Reverse dependencies', 'Recipe']:
-            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
-        # step 5-6
-        self.driver.find_element_by_id("edit-columns-button").click()
-        selectable_class = 'checkbox'
-        # minimum-table : means unselectable items
-        unselectable_class = 'checkbox muted'
-        selectable_check_list = ['Dependencies', 'Layer', 'Layer branch', 'Layer commit', \
-                                 'License', 'Recipe', 'Recipe version', 'Reverse dependencies', \
-                                 'Size', 'Size over total (%)']
-        unselectable_check_list = ['Package', 'Package version']
-        selectable_list = list()
-        unselectable_list = list()
-        selectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + selectable_class + "']")
-        unselectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + unselectable_class + "']")
-        for element in selectable_elements:
-            selectable_list.append(element.text)
-        for element in unselectable_elements:
-            unselectable_list.append(element.text)
-        # check them
-        for item in selectable_check_list:
-            self.assertTrue(item in selectable_list, msg=("%s not found in dropdown menu" % item))
-        for item in unselectable_check_list:
-            self.assertTrue(item in unselectable_list, msg=("%s not found in dropdown menu" % item))
-        self.driver.find_element_by_id("edit-columns-button").click()
-        # step 7
-        self.driver.find_element_by_partial_link_text("Directory structure").click()
-        head_list = self.get_table_head_text('dirtable')
-        for item in ['Directory / File', 'Symbolic link to', 'Source package', 'Size', 'Permissions', 'Owner', 'Group']:
-            self.assertTrue(item in head_list, msg=("%s not found in Directory structure table head" % item))
-
-        ##############
-        #  CASE 950  #
-        ##############
-    def test_950(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # step3&4: so far we're not sure if there's "successful build" or "failed
-        # build".If either of them doesn't exist, we can still go on other steps
-        check_list = ['Configuration', 'Tasks', 'Recipes', 'Packages', 'Time', 'CPU usage', 'Disk I/O']
-        has_successful_build = 1
-        has_failed_build = 1
-        try:
-            pass_icon = self.driver.find_element_by_xpath("//*[@class='icon-ok-sign success']")
-        except Exception:
-            self.log.info("no successful build exists")
-            has_successful_build = 0
-            pass
-        if has_successful_build:
-            pass_icon.click()
-            # save screen here to check if it matches requirement.
-            self.browser_delay()
-            self.save_screenshot(screenshot_type='selenium', append_name='step3_1')
-            for item in check_list:
-                try:
-                    self.find_element_by_link_text_in_table('nav', item)
-                except Exception:
-                    self.assertFalse(True, msg=("link  %s cannot be found in the page" % item))
-            # step 6
-            check_list_2 = ['Packages included', 'Total package size', \
-                      'License manifest', 'Image files']
-            self.assertTrue(self.is_text_present(check_list_2), msg=("text not in web page"))
-            self.driver.back()
-        try:
-            fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
-        except Exception:
-            has_failed_build = 0
-            self.log.info("no failed build exists")
-            pass
-        if has_failed_build:
-            fail_icon.click()
-            # save screen here to check if it matches requirement.
-            self.browser_delay()
-            self.save_screenshot(screenshot_type='selenium', append_name='step3_2')
-            for item in check_list:
-                try:
-                    self.find_element_by_link_text_in_table('nav', item)
-                except Exception:
-                    self.assertFalse(True, msg=("link  %s cannot be found in the page" % item))
-            # step 7 involved
-            check_list_3 = ['Machine', 'Distro', 'Layers', 'Total number of tasks', 'Tasks executed', \
-                      'Tasks not executed', 'Reuse', 'Recipes built', 'Packages built']
-            self.assertTrue(self.is_text_present(check_list_3), msg=("text not in web page"))
-            self.driver.back()
-
-
-        ##############
-        #  CASE 951  #
-        ##############
-    def test_951(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # currently test case itself isn't responsible for creating "1 successful and
-        # 1 failed build"
-        has_successful_build = 1
-        has_failed_build = 1
-        try:
-            fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
-        except Exception:
-            has_failed_build = 0
-            self.log.info("no failed build exists")
-            pass
-        # if there's failed build, we can proceed
-        if has_failed_build:
-            self.driver.find_element_by_partial_link_text("error").click()
-            self.driver.back()
-        # not sure if there "must be" some warnings, so here save a screen
-        self.browser_delay()
-        self.save_screenshot(screenshot_type='selenium', append_name='step4')
-
-
-        ##############
-        #  CASE 955  #
-        ##############
-    def test_955(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.log.info(" You should manually create all images before test starts!")
-        # So far the case itself is not responsable for creating all sorts of images.
-        # So assuming they are already there
-        # step 2
-        self.driver.find_element_by_link_text("core-image-minimal").click()
-        # save screen here to see the page component
-
-
-        ##############
-        #  CASE 956  #
-        ##############
-    def test_956(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        # step 2-3 need to run manually
-        self.log.info("step 2-3: checking the help message when you hover on help icon of target,\
-                       tasks, recipes, packages need to run manually")
-        self.driver.find_element_by_partial_link_text("Manual").click()
-        if not self.is_text_present("Manual"):
-            self.log.error("please check [Toaster manual] link on page")
-            self.failIf(True)
-
-####################################################################################################
-# Starting backend tests ###########################################################################
-####################################################################################################
-
-        ##############
-        #  CASE 1066 #
-        ##############
-    def test_1066(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select count(name) from orm_project a, auth_user b where a.user_id = b.id and b.username='_anonuser';"
-        cursor.execute(query)
-        data = cursor.fetchone()
-        self.failUnless(data >= 1)
-
-
-        ##############
-        #  CASE 1071 #
-        ##############
-    def test_1071(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select name from orm_release;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        for i in range(0,4):
-            data[i] = data[i][0]
-        data.sort()
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_data = []
-        for i in range (0,4):
-            json_data.append(json_parse['releases'][i]['name'])
-        json_data.sort()
-        print json_data
-        self.failUnless(data == json_data)
-
-        ##############
-        #  CASE 1072 #
-        ##############
-    def test_1072(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select value from orm_toastersetting where name like 'DEFCONF%';"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        for i in range(0,6):
-            data[i] = data[i][0]
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_data=json_parse['config']
-        json_data = json_data.values()
-        print json_data
-        self.failUnless(data == json_data)
-
-
-        ##############
-        #  CASE 1074 #
-        ##############
-    def test_1074(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select name from orm_layersource;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        for i in range(0,3):
-            data[i] = data[i][0]
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_data = []
-        for i in range(0,3):
-            json_data.append(json_parse['layersources'][i]['name'])
-        print json_data
-        self.failUnless(set(data) == set(json_data))
-
-        ##############
-        #  CASE 1075 #
-        ##############
-    def test_1075(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select value from orm_toastersetting where name like 'DEFAULT_RELEASE';"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        data = data[0][0]
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_data = json_parse['defaultrelease']
-        print json_data
-        self.failUnless(set(data) == set(json_data))
-
-        ##############
-        #  CASE 1076 #
-        ##############
-    def test_1076(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-
-        print 'Checking branches for "Local Yocto Project"'
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select name from orm_branch where layer_source_id=1;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        lenght = len(data)
-        try:
-            for i in range(0,lenght):
-                data[i] = data[i][0]
-        except:
-            pass
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_location = json_parse['layersources'][0]['name']
-        print json_location
-        json_data = json_parse['layersources'][0]['branches']
-        print json_data
-        self.failUnless(set(data) == set(json_data))
-
-        print 'Checking branches for "OpenEmbedded"'
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select name from orm_branch where layer_source_id=3;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        lenght = len(data)
-        for i in range(0,lenght):
-            data[i] = data[i][0]
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_location = json_parse['layersources'][1]['name']
-        print json_location
-        json_data = json_parse['layersources'][1]['branches']
-        print json_data
-        self.failUnless(set(data) == set(json_data))
-
-        print 'Checking branches for "Imported layers"'
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select name from orm_branch where layer_source_id=2;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        lenght = len(data)
-        for i in range(0,lenght):
-            data[i] = data[i][0]
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_location = json_parse['layersources'][2]['name']
-        print json_location
-        json_data = json_parse['layersources'][2]['branches']
-        print json_data
-        self.failUnless(set(data) == set(json_data))
-
-
-        ##############
-        #  CASE 1077 #
-        ##############
-    def test_1077(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select name from orm_bitbakeversion;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        for i in range(0,4):
-            data[i] = data[i][0]
-        print data
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_data = []
-        for i in range(0,4):
-            json_data.append(json_parse['bitbake'][i]['name'])
-        print json_data
-        self.failUnless(set(data) == set(json_data))
-
-        ##############
-        #  CASE 1083 #
-        ##############
-    def test_1083(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_id("new-project-button").click()
-        self.driver.find_element_by_id("new-project-name").send_keys("new-test-project")
-        self.driver.find_element_by_id("create-project-button").click()
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select count(name) from orm_project where name = 'new-test-project';"
-        cursor.execute(query)
-        data = cursor.fetchone()
-        print 'data: %s' % data
-        self.failUnless(data >= 1)
-
-        ##############
-        #  CASE 1084 #
-        ##############
-    def test_1084(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_id("new-project-button").click()
-        self.driver.find_element_by_id("new-project-name").send_keys("new-default-project")
-        self.driver.find_element_by_id("create-project-button").click()
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select a.name from orm_release a, orm_project b where a.id = b.release_id and b.name = 'new-default-project' limit 1;"
-        cursor.execute(query)
-        db_data = str(cursor.fetchone()[0])
-        json_parse = json.loads(open('toasterconf.json').read())
-        json_data = str(json_parse['defaultrelease'])
-        self.failUnless(db_data == json_data)
-
-        ##############
-        #  CASE 1088 #
-        ##############
-    def test_1088(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
-        self.driver.find_element_by_link_text('new-default-project').click()
-        self.driver.find_element_by_id('project-change-form-toggle').click()
-        self.driver.find_element_by_id('project-name-change-input').clear()
-        self.driver.find_element_by_id('project-name-change-input').send_keys('new-name')
-        self.driver.find_element_by_id('project-name-change-btn').click()
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select count(name) from orm_project where name = 'new-name';"
-        cursor.execute(query)
-        data = cursor.fetchone()[0]
-        self.failUnless(data == 1)
-        #reseting project name
-        self.driver.find_element_by_id('project-change-form-toggle').click()
-        self.driver.find_element_by_id('project-name-change-input').clear()
-        self.driver.find_element_by_id('project-name-change-input').send_keys('new-default-project')
-        self.driver.find_element_by_id('project-name-change-btn').click()
-
-
-        ##############
-        #  CASE 1089 #
-        ##############
-    def test_1089(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
-        self.driver.find_element_by_link_text('new-default-project').click()
-        self.driver.find_element_by_id('change-machine-toggle').click()
-        self.driver.find_element_by_id('machine-change-input').clear()
-        self.driver.find_element_by_id('machine-change-input').send_keys('qemuarm64')
-#        self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
-        self.driver.find_element_by_id('machine-change-btn').click()
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select count(id) from orm_projectvariable where name like 'machine' and value like 'qemuarm64';"
-        cursor.execute(query)
-        data = cursor.fetchone()[0]
-        self.failUnless(data == 1)
-        #resetting machine to default value
-        self.driver.find_element_by_id('change-machine-toggle').click()
-        self.driver.find_element_by_id('machine-change-input').clear()
-        self.driver.find_element_by_id('machine-change-input').send_keys('qemux86')
-        self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
-        self.driver.find_element_by_id('machine-change-btn').click()
-
-        ##############
-        #  CASE 1090 #
-        ##############
-    def test_1090(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select username from auth_user where is_superuser = 1;"
-        cursor.execute(query)
-        data = cursor.fetchall()
-        try:
-            data = data[0][0]
-        except:
-            pass
-        print data
-        self.failUnless(data == 'toaster_admin')
-
-        ##############
-        #  CASE 1091 #
-        ##############
-    def test_1091(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        self.driver.get(self.base_url)
-        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
-        self.driver.find_element_by_link_text('new-default-project').click()
-        self.driver.find_element_by_id('release-change-toggle').click()
-        dropdown = self.driver.find_element_by_css_selector('select')
-        for option in dropdown.find_elements_by_tag_name('option'):
-            if option.text == 'Local Yocto Project':
-                option.click()
-        self.driver.find_element_by_id('change-release-btn').click()
-        #wait for the changes to register in the DB
-        time.sleep(1)
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select count(*) from orm_layer_version a, orm_projectlayer b, orm_project c where a.\"commit\"=\"HEAD\" and a.id = b.layercommit_id and b.project_id=c.id and c.name='new-default-project';"
-        cursor.execute(query)
-        data = cursor.fetchone()[0]
-        #resetting release to default
-        self.driver.find_element_by_id('release-change-toggle').click()
-        dropdown = self.driver.find_element_by_css_selector('select')
-        for option in dropdown.find_elements_by_tag_name('option'):
-            if option.text == 'Yocto Project master':
-                option.click()
-        self.driver.find_element_by_id('change-release-btn').click()
-        #wait for the changes to register in the DB
-        time.sleep(1)
-        self.failUnless(data == 3)
-
-        ##############
-        #  CASE 1092 #
-        ##############
-    def test_1092(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-        self.driver.maximize_window()
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select a.name, a.value from orm_projectvariable a, orm_project b where a.project_id = b.id and b.name = 'new-default-project';"
-        cursor.execute(query)
-        data = dict(cursor.fetchall())
-        print data
-        default_values = {u'IMAGE_INSTALL_append': u'', u'PACKAGE_CLASSES': u'package_rpm', u'MACHINE': u'qemux86', u'SDKMACHINE': u'x86_64', u'DISTRO': u'poky', u'IMAGE_FSTYPES': u'ext3 jffs2 tar.bz2'}
-        self.failUnless(data == default_values)
-
-        ##############
-        #  CASE 1093 #
-        ##############
-    def test_1093(self):
-        self.case_no = self.get_case_number()
-        self.log.info(' CASE %s log: ' % str(self.case_no))
-
-        #get initial values
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
-        cursor.execute(query)
-        data_initial = cursor.fetchall()
-        print data_initial
-
-        self.driver.maximize_window()
-        self.driver.get('localhost:8000')#self.base_url)
-        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
-        self.driver.find_element_by_link_text('new-default-project').click()
-        self.driver.find_element_by_id('release-change-toggle').click()
-        dropdown = self.driver.find_element_by_css_selector('select')
-        for option in dropdown.find_elements_by_tag_name('option'):
-            if option.text == 'Local Yocto Project':
-                option.click()
-        self.driver.find_element_by_id('change-release-btn').click()
-        #wait for the changes to register in the DB
-        time.sleep(1)
-
-        #get changed values
-        con=sqlite.connect('toaster.sqlite')
-        cursor = con.cursor()
-        query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
-        cursor.execute(query)
-        data_changed = cursor.fetchall()
-        print data_changed
-
-        #resetting release to default
-        self.driver.find_element_by_id('release-change-toggle').click()
-        dropdown = self.driver.find_element_by_css_selector('select')
-        for option in dropdown.find_elements_by_tag_name('option'):
-            if option.text == 'Yocto Project master':
-                option.click()
-        self.driver.find_element_by_id('change-release-btn').click()
-        #wait for the changes to register in the DB
-        time.sleep(1)
-        self.failUnless(data_initial != data_changed)
diff --git a/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg b/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
deleted file mode 100644
index 685a9ee..0000000
--- a/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
+++ /dev/null
@@ -1,25 +0,0 @@
-# Configuration file for toaster_test
-# Sorted by different host type
-
-# test browser could be: firefox; chrome; ie(still under development)
-# logging_level could be: CRITICAL; ERROR; WARNING; INFO; DEBUG; NOTSET
-
-
-[toaster_test_linux]
-toaster_url = 'http://127.0.0.1:8000'
-test_browser = 'firefox'
-test_cases = [946]
-logging_level = 'INFO'
-
-
-[toaster_test_windows]
-toaster_url = 'http://127.0.0.1:8000'
-test_browser = ['ie', 'firefox', 'chrome']
-test_cases = [901, 902, 903]
-logging_level = 'DEBUG'
-
-[toaster_test_darwin]
-toaster_url = 'http://127.0.0.1:8000'
-test_browser = 'firefox'
-test_cases = [901, 902, 903, 904, 906, 910, 911, 912, 913, 914, 915, 916, 923, 924, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 955, 956]
-logging_level = 'INFO'
diff --git a/lib/toaster/tests/toaster_test.cfg b/lib/toaster/tests/toaster_test.cfg
new file mode 100644
index 0000000..6acd166
--- /dev/null
+++ b/lib/toaster/tests/toaster_test.cfg
@@ -0,0 +1,25 @@
+# Configuration file for toaster_test
+# Sorted by different host type
+
+# test browser could be: firefox; chrome; ie(still under development)
+# logging_level could be: CRITICAL; ERROR; WARNING; INFO; DEBUG; NOTSET
+
+
+[toaster_test_linux]
+toaster_url = 'http://localhost:8000'
+test_browser = 'firefox'
+test_cases = [946]
+logging_level = 'INFO'
+
+
+[toaster_test_windows]
+toaster_url = 'http://localhost:8000'
+test_browser = ['ie', 'firefox', 'chrome']
+test_cases = [901, 902, 903]
+logging_level = 'DEBUG'
+
+[toaster_test_darwin]
+toaster_url = 'http://localhost:8000'
+test_browser = 'firefox'
+test_cases = [901, 902, 903, 904, 906, 910, 911, 912, 913, 914, 915, 916, 923, 924, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 955, 956]
+logging_level = 'INFO'
diff --git a/lib/toaster/tests/ui.py b/lib/toaster/tests/ui.py
new file mode 100644
index 0000000..d8f838a
--- /dev/null
+++ b/lib/toaster/tests/ui.py
@@ -0,0 +1,2376 @@
+#!/usr/bin/python
+# Copyright
+
+# DESCRIPTION
+# This is toaster automation base class and test cases file
+
+# History:
+# 2015.03.09  inital version
+# 2015.03.23  adding toaster_test.cfg, run_toastertest.py so we can run case by case from outside
+
+# Briefs:
+# This file is comprised of 3 parts:
+# I:   common utils like sorting, getting attribute.. etc
+# II:  base class part, which complies with unittest frame work and
+#      contains class selenium-based functions
+# III: test cases
+#      to add new case: just implement new test_xxx() function in class toaster_cases
+
+# NOTES for cases:
+# case 946:
+# step 6 - 8 needs to be observed using screenshots
+# case 956:
+# step 2 - 3 needs to be run manually
+
+import unittest, time, re, sys, getopt, os, logging, string, errno, exceptions
+import shutil, argparse, ConfigParser, platform, json
+from selenium import webdriver
+from selenium.common.exceptions import NoSuchElementException
+from selenium import selenium
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support.ui import Select
+import sqlite3 as sqlite
+
+
+###########################################
+#                                         #
+# PART I: utils stuff                     #
+#                                         #
+###########################################
+
+class Listattr(object):
+    """
+    Set of list attribute. This is used to determine what the list content is.
+    Later on we may add more attributes here.
+    """
+    NULL = "null"
+    NUMBERS = "numbers"
+    STRINGS = "strings"
+    PERCENT = "percentage"
+    SIZE = "size"
+    UNKNOWN = "unknown"
+
+
+def get_log_root_dir():
+    max_depth = 5
+    parent_dir = '../'
+    for number in range(0, max_depth):
+        if os.path.isdir(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log'):
+            log_root_dir = os.path.abspath(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log')
+            break
+
+    if number == (max_depth - 1):
+        print 'No log dir found. Please check'
+        raise Exception
+
+    return log_root_dir
+
+
+def mkdir_p(dir):
+    try:
+        os.makedirs(dir)
+    except OSError as exc:
+        if exc.errno == errno.EEXIST and os.path.isdir(dir):
+            pass
+        else:
+            raise
+
+
+def get_list_attr(testlist):
+    """
+    To determine the list content
+    """
+    if not testlist:
+        return Listattr.NULL
+    listtest = testlist[:]
+    try:
+        listtest.remove('')
+    except ValueError:
+        pass
+    pattern_percent = re.compile(r"^([0-9])+(\.)?([0-9])*%$")
+    pattern_size = re.compile(r"^([0-9])+(\.)?([0-9])*( )*(K)*(M)*(G)*B$")
+    pattern_number = re.compile(r"^([0-9])+(\.)?([0-9])*$")
+    def get_patterned_number(pattern, tlist):
+        count = 0
+        for item in tlist:
+            if re.search(pattern, item):
+                count += 1
+        return count
+    if get_patterned_number(pattern_percent, listtest) == len(listtest):
+        return Listattr.PERCENT
+    elif get_patterned_number(pattern_size, listtest) == len(listtest):
+        return Listattr.SIZE
+    elif get_patterned_number(pattern_number, listtest) == len(listtest):
+        return Listattr.NUMBERS
+    else:
+        return Listattr.STRINGS
+
+
+def is_list_sequenced(testlist):
+    """
+    Function to tell if list is sequenced
+    Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
+    Each has respective way to determine if it's sequenced.
+    """
+    test_list = testlist[:]
+    try:
+        test_list.remove('')
+    except ValueError:
+        pass
+
+    if get_list_attr(testlist) == Listattr.NULL :
+        return True
+
+    elif get_list_attr(testlist) == Listattr.STRINGS :
+        return (sorted(test_list) == test_list)
+
+    elif get_list_attr(testlist) == Listattr.NUMBERS :
+        list_number = []
+        for item in test_list:
+            list_number.append(eval(item))
+        return (sorted(list_number) == list_number)
+
+    elif get_list_attr(testlist) == Listattr.PERCENT :
+        list_number = []
+        for item in test_list:
+            list_number.append(eval(item.strip('%')))
+        return (sorted(list_number) == list_number)
+
+    elif get_list_attr(testlist) == Listattr.SIZE :
+        list_number = []
+        # currently SIZE is splitted by space
+        for item in test_list:
+            if item.split()[1].upper() == "KB":
+                list_number.append(1024 * eval(item.split()[0]))
+            elif item.split()[1].upper() == "MB":
+                list_number.append(1024 * 1024 * eval(item.split()[0]))
+            elif item.split()[1].upper() == "GB":
+                list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
+            else:
+                list_number.append(eval(item.split()[0]))
+        return (sorted(list_number) == list_number)
+
+    else:
+        print 'Unrecognized list type, please check'
+        return False
+
+
+def is_list_inverted(testlist):
+    """
+    Function to tell if list is inverted
+    Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
+    Each has respective way to determine if it's inverted.
+    """
+    test_list = testlist[:]
+    try:
+        test_list.remove('')
+    except ValueError:
+        pass
+
+    if get_list_attr(testlist) == Listattr.NULL :
+        return True
+
+    elif get_list_attr(testlist) == Listattr.STRINGS :
+        return (sorted(test_list, reverse = True) == test_list)
+
+    elif get_list_attr(testlist) == Listattr.NUMBERS :
+        list_number = []
+        for item in test_list:
+            list_number.append(eval(item))
+        return (sorted(list_number, reverse = True) == list_number)
+
+    elif get_list_attr(testlist) == Listattr.PERCENT :
+        list_number = []
+        for item in test_list:
+            list_number.append(eval(item.strip('%')))
+        return (sorted(list_number, reverse = True) == list_number)
+
+    elif get_list_attr(testlist) == Listattr.SIZE :
+        list_number = []
+        # currently SIZE is splitted by space. such as 0 B; 1 KB; 2 MB
+        for item in test_list:
+            if item.split()[1].upper() == "KB":
+                list_number.append(1024 * eval(item.split()[0]))
+            elif item.split()[1].upper() == "MB":
+                list_number.append(1024 * 1024 * eval(item.split()[0]))
+            elif item.split()[1].upper() == "GB":
+                list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
+            else:
+                list_number.append(eval(item.split()[0]))
+        return (sorted(list_number, reverse = True) == list_number)
+
+    else:
+        print 'Unrecognized list type, please check'
+        return False
+
+def replace_file_content(filename, item, option):
+    f = open(filename)
+    lines = f.readlines()
+    f.close()
+    output = open(filename, 'w')
+    for line in lines:
+        if line.startswith(item):
+            output.write(item + " = '" + option + "'\n")
+        else:
+            output.write(line)
+    output.close()
+
+def extract_number_from_string(s):
+    """
+    extract the numbers in a string. return type is 'list'
+    """
+    return re.findall(r'([0-9]+)', s)
+
+# Below is decorator derived from toaster backend test code
+class NoParsingFilter(logging.Filter):
+    def filter(self, record):
+        return record.levelno == 100
+
+def LogResults(original_class):
+    orig_method = original_class.run
+
+    from time import strftime, gmtime
+    caller = 'toaster'
+    timestamp = strftime('%Y%m%d%H%M%S',gmtime())
+    logfile = os.path.join(os.getcwd(),'results-'+caller+'.'+timestamp+'.log')
+    linkfile = os.path.join(os.getcwd(),'results-'+caller+'.log')
+
+    #rewrite the run method of unittest.TestCase to add testcase logging
+    def run(self, result, *args, **kws):
+        orig_method(self, result, *args, **kws)
+        passed = True
+        testMethod = getattr(self, self._testMethodName)
+        #if test case is decorated then use it's number, else use it's name
+        try:
+            test_case = testMethod.test_case
+        except AttributeError:
+            test_case = self._testMethodName
+
+        class_name = str(testMethod.im_class).split("'")[1]
+
+        #create custom logging level for filtering.
+        custom_log_level = 100
+        logging.addLevelName(custom_log_level, 'RESULTS')
+
+        def results(self, message, *args, **kws):
+            if self.isEnabledFor(custom_log_level):
+                self.log(custom_log_level, message, *args, **kws)
+        logging.Logger.results = results
+
+        logging.basicConfig(filename=logfile,
+                            filemode='w',
+                            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+                            datefmt='%H:%M:%S',
+                            level=custom_log_level)
+        for handler in logging.root.handlers:
+            handler.addFilter(NoParsingFilter())
+        local_log = logging.getLogger(caller)
+
+        #check status of tests and record it
+
+        for (name, msg) in result.errors:
+            if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
+                local_log.results("Testcase "+str(test_case)+": ERROR")
+                local_log.results("Testcase "+str(test_case)+":\n"+msg)
+                passed = False
+        for (name, msg) in result.failures:
+            if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
+                local_log.results("Testcase "+str(test_case)+": FAILED")
+                local_log.results("Testcase "+str(test_case)+":\n"+msg)
+                passed = False
+        for (name, msg) in result.skipped:
+            if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
+                local_log.results("Testcase "+str(test_case)+": SKIPPED")
+                passed = False
+        if passed:
+            local_log.results("Testcase "+str(test_case)+": PASSED")
+
+        # Create symlink to the current log
+        if os.path.exists(linkfile):
+            os.remove(linkfile)
+        os.symlink(logfile, linkfile)
+
+    original_class.run = run
+
+    return original_class
+
+
+###########################################
+#                                         #
+# PART II: base class                     #
+#                                         #
+###########################################
+
+@LogResults
+class toaster_cases_base(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.log = cls.logger_create()
+
+    def setUp(self):
+        self.screenshot_sequence = 1
+        self.verificationErrors = []
+        self.accept_next_alert = True
+        self.host_os = platform.system().lower()
+        if os.getenv('TOASTER_SUITE'):
+            self.target_suite = os.getenv('TOASTER_SUITE')
+        else:
+            self.target_suite = self.host_os
+
+        self.parser = ConfigParser.SafeConfigParser()
+        self.parser.read('toaster_test.cfg')
+        self.base_url = eval(self.parser.get('toaster_test_' + self.target_suite, 'toaster_url'))
+
+        # create log dir . Currently , we put log files in log/tmp. After all
+        # test cases are done, move them to log/$datetime dir
+        self.log_tmp_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + 'tmp'
+        try:
+            mkdir_p(self.log_tmp_dir)
+        except OSError :
+            logging.error("%(asctime)s Cannot create tmp dir under log, please check your privilege")
+        # self.log = self.logger_create()
+        # driver setup
+        self.setup_browser()
+
+    @staticmethod
+    def logger_create():
+        log_file = "toaster-auto-" + time.strftime("%Y%m%d%H%M%S") + ".log"
+        if os.path.exists("toaster-auto.log"): os.remove("toaster-auto.log")
+        os.symlink(log_file, "toaster-auto.log")
+
+        log = logging.getLogger("toaster")
+        log.setLevel(logging.DEBUG)
+
+        fh = logging.FileHandler(filename=log_file, mode='w')
+        fh.setLevel(logging.DEBUG)
+
+        ch = logging.StreamHandler(sys.stdout)
+        ch.setLevel(logging.INFO)
+
+        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+        fh.setFormatter(formatter)
+        ch.setFormatter(formatter)
+
+        log.addHandler(fh)
+        log.addHandler(ch)
+
+        return log
+
+
+    def setup_browser(self, *browser_path):
+        self.browser = eval(self.parser.get('toaster_test_' + self.target_suite, 'test_browser'))
+        print self.browser
+        if self.browser == "firefox":
+            driver = webdriver.Firefox()
+        elif self.browser == "chrome":
+            driver = webdriver.Chrome()
+        elif self.browser == "ie":
+            driver = webdriver.Ie()
+        else:
+            driver = None
+            print "unrecognized browser type, please check"
+        self.driver = driver
+        self.driver.implicitly_wait(30)
+        return self.driver
+
+
+    def save_screenshot(self,  **log_args):
+        """
+        This function is used to save screen either by os interface or selenium interface.
+        How to use:
+        self.save_screenshot(screenshot_type = 'native'/'selenium', log_sub_dir = 'xxx',
+                             append_name = 'stepx')
+        where native means screenshot func provided by OS,
+        selenium means screenshot func provided by selenium webdriver
+        """
+        types = [log_args.get('screenshot_type')]
+        # when no screenshot_type is specified
+        if types == [None]:
+            types = ['native', 'selenium']
+        # normally append_name is used to specify which step..
+        add_name = log_args.get('append_name')
+        if not add_name:
+            add_name = '-'
+        # normally there's no need to specify sub_dir
+        sub_dir = log_args.get('log_sub_dir')
+        if not sub_dir:
+            # use casexxx as sub_dir name
+            sub_dir = 'case' + str(self.case_no)
+        for item in types:
+            log_dir = self.log_tmp_dir + os.sep + sub_dir
+            mkdir_p(log_dir)
+            log_path = log_dir + os.sep +  self.browser + '-' +\
+                    item + '-' + add_name + '-' + str(self.screenshot_sequence) + '.png'
+            if item == 'native':
+                if self.host_os == "linux":
+                    os.system("scrot " + log_path)
+                elif self.host_os=="darwin":
+                    os.system("screencapture -x " + log_path)
+            elif item == 'selenium':
+                self.driver.get_screenshot_as_file(log_path)
+            self.screenshot_sequence += 1
+
+    def browser_delay(self):
+        """
+        currently this is a workaround for some chrome test.
+        Sometimes we need a delay to accomplish some operation.
+        But for firefox, mostly we don't need this.
+        To be discussed
+        """
+        if self.browser == "chrome":
+            time.sleep(1)
+        return
+
+
+# these functions are not contained in WebDriver class..
+    def find_element_by_text(self, string):
+        return self.driver.find_element_by_xpath("//*[text()='" + string + "']")
+
+
+    def find_elements_by_text(self, string):
+        return self.driver.find_elements_by_xpath("//*[text()='" + string + "']")
+
+
+    def find_element_by_text_in_table(self, table_id, text_string):
+        """
+        This is used to search some certain 'text' in certain table
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element = table_element.find_element_by_xpath("//*[text()='" + text_string + "']")
+        except NoSuchElementException, e:
+            print 'no element found'
+            raise
+        return element
+
+
+    def find_element_by_link_text_in_table(self, table_id, link_text):
+        """
+        Assume there're multiple suitable "find_element_by_link_text".
+        In this circumstance we need to specify "table".
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element = table_element.find_element_by_link_text(link_text)
+        except NoSuchElementException, e:
+            print 'no element found'
+            raise
+        return element
+
+
+    def find_elements_by_link_text_in_table(self, table_id, link_text):
+        """
+        Search link-text in certain table. This helps to narrow down search area.
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element_list = table_element.find_elements_by_link_text(link_text)
+        except NoSuchElementException, e:
+            print 'no element found'
+            raise
+        return element_list
+
+
+    def find_element_by_partial_link_text_in_table(self, table_id, link_text):
+        """
+        Search element by partial link text in certain table.
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element = table_element.find_element_by_partial_link_text(link_text)
+            return element
+        except NoSuchElementException, e:
+            print 'no element found'
+            raise
+
+
+    def find_elements_by_partial_link_text_in_table(self, table_id, link_text):
+        """
+        Assume there're multiple suitable "find_partial_element_by_link_text".
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element_list = table_element.find_elements_by_partial_link_text(link_text)
+            return element_list
+        except NoSuchElementException, e:
+            print 'no element found'
+            raise
+
+
+    def find_element_by_xpath_in_table(self, table_id, xpath):
+        """
+        This helps to narrow down search area. Especially useful when dealing with pop-up form.
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element = table_element.find_element_by_xpath(xpath)
+        except NoSuchElementException, e:
+            print 'no element found'
+            raise
+        return element
+
+
+    def find_elements_by_xpath_in_table(self, table_id, xpath):
+        """
+        This helps to narrow down search area. Especially useful when dealing with pop-up form.
+        """
+        try:
+            table_element = self.get_table_element(table_id)
+            element_list = table_element.find_elements_by_xpath(xpath)
+        except NoSuchElementException, e:
+            print 'no elements found'
+            raise
+        return element_list
+
+
+    def shortest_xpath(self, pname, pvalue):
+        return "//*[@" + pname + "='" + pvalue + "']"
+
+
+#usually elements in the same column are with same class name. for instance: class="outcome" .TBD
+    def get_table_column_text(self, attr_name, attr_value):
+        c_xpath = self.shortest_xpath(attr_name, attr_value)
+        elements = self.driver.find_elements_by_xpath(c_xpath)
+        c_list = []
+        for element in elements:
+            c_list.append(element.text)
+        return c_list
+
+
+    def get_table_column_text_by_column_number(self, table_id, column_number):
+        c_xpath = "//*[@id='" + table_id + "']//td[" + str(column_number) + "]"
+        elements = self.driver.find_elements_by_xpath(c_xpath)
+        c_list = []
+        for element in elements:
+            c_list.append(element.text)
+        return c_list
+
+
+    def get_table_head_text(self, *table_id):
+#now table_id is a tuple...
+        if table_id:
+            thead_xpath = "//*[@id='" + table_id[0] + "']//thead//th[text()]"
+            elements = self.driver.find_elements_by_xpath(thead_xpath)
+            c_list = []
+            for element in elements:
+                if element.text:
+                    c_list.append(element.text)
+            return c_list
+#default table on page
+        else:
+            return self.driver.find_element_by_xpath("//*/table/thead").text
+
+
+
+    def get_table_element(self, table_id, *coordinate):
+        if len(coordinate) == 0:
+#return whole-table element
+            element_xpath = "//*[@id='" + table_id + "']"
+            try:
+                element = self.driver.find_element_by_xpath(element_xpath)
+            except NoSuchElementException, e:
+                raise
+            return element
+        row = coordinate[0]
+
+        if len(coordinate) == 1:
+#return whole-row element
+            element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]"
+            try:
+                element = self.driver.find_element_by_xpath(element_xpath)
+            except NoSuchElementException, e:
+                return False
+            return element
+#now we are looking for an element with specified X and Y
+        column = coordinate[1]
+
+        element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]"
+        try:
+            element = self.driver.find_element_by_xpath(element_xpath)
+        except NoSuchElementException, e:
+            return False
+        return element
+
+
+    def get_table_data(self, table_id, row_count, column_count):
+        row = 1
+        Lists = []
+        while row <= row_count:
+            column = 1
+            row_content=[]
+            while column <= column_count:
+                s= "//*[@id='" + table_id + "']/tbody/tr[" + str(row) +"]/td[" + str(column) + "]"
+                v = self.driver.find_element_by_xpath(s).text
+                row_content.append(v)
+                column = column + 1
+                print("row_content=",row_content)
+            Lists.extend(row_content)
+            print Lists[row-1][0]
+            row = row + 1
+        return Lists
+
+    # The is_xxx_present functions only returns True/False
+    # All the log work is done in test procedure, so we can easily trace back
+    # using logging
+    def is_text_present (self, patterns):
+        for pattern in patterns:
+            if str(pattern) not in self.driver.page_source:
+                print 'Text "'+pattern+'" is missing'
+                return False
+        return True
+
+
+    def is_element_present(self, how, what):
+        try:
+            self.driver.find_element(how, what)
+        except NoSuchElementException, e:
+            print 'Could not find element '+str(what)+' by ' + str(how)
+            return False
+        return True
+
+
+    def is_alert_present(self):
+        try: self.driver.switch_to_alert()
+        except NoAlertPresentException, e: return False
+        return True
+
+
+    def close_alert_and_get_its_text(self):
+        try:
+            alert = self.driver.switch_to_alert()
+            alert_text = alert.text
+            if self.accept_next_alert:
+                alert.accept()
+            else:
+                alert.dismiss()
+            return alert_text
+        finally: self.accept_next_alert = True
+
+
+    def get_case_number(self):
+        """
+        what case are we running now
+        """
+        funcname = sys._getframe(1).f_code.co_name
+        caseno_str = funcname.strip('test_')
+        try:
+            caseno = int(caseno_str)
+        except ValueError:
+            print "get case number error! please check if func name is test_xxx"
+            return False
+        return caseno
+
+
+    def tearDown(self):
+        self.log.info(' END: CASE %s log \n\n' % str(self.case_no))
+        self.driver.quit()
+        self.assertEqual([], self.verificationErrors)
+
+
+###################################################################
+#                                                                 #
+# PART III: test cases                                            #
+# please refer to                                                 #
+# https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=xxx  #
+#                                                                 #
+###################################################################
+
+# Note: to comply with the unittest framework, we call these test_xxx functions
+# from run_toastercases.py to avoid calling setUp() and tearDown() multiple times
+
+
+class toaster_cases(toaster_cases_base):
+        ##############
+        #  CASE 901  #
+        ##############
+    def test_901(self):
+        # the reason why get_case_number is not in setUp function is that
+        # otherwise it returns "setUp" instead of "test_xxx"
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # open all columns
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # adding explicitly wait for chromedriver..-_-
+        self.browser_delay()
+        self.driver.find_element_by_id("started_on").click()
+        self.browser_delay()
+        self.driver.find_element_by_id("time").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # dict: {lint text name : actual class name}
+        table_head_dict = {'Outcome':'outcome', 'Recipe':'target', 'Machine':'machine', 'Started on':'started_on', 'Completed on':'completed_on', \
+                'Errors':'errors_no', 'Warnings':'warnings_no', 'Time':'time'}
+        for key in table_head_dict:
+            try:
+                self.driver.find_element_by_link_text(key).click()
+            except Exception, e:
+                self.log.error("%s cannot be found on page" % key)
+                raise
+            column_list = self.get_table_column_text("class", table_head_dict[key])
+            # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
+            # the point is, after another click, it should be another order
+            if is_list_inverted(column_list):
+                self.driver.find_element_by_link_text(key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
+            else:
+                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not sequenced" % key))
+                self.driver.find_element_by_link_text(key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
+        self.log.info("case passed")
+
+
+        ##############
+        #  CASE 902  #
+        ##############
+    def test_902(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # Could add more test patterns here in the future. Also, could search some items other than target column in future..
+        patterns = ["minimal", "sato"]
+        for pattern in patterns:
+            ori_target_column_texts = self.get_table_column_text("class", "target")
+            print ori_target_column_texts
+            self.driver.find_element_by_id("search").clear()
+            self.driver.find_element_by_id("search").send_keys(pattern)
+            self.driver.find_element_by_id("search-button").click()
+            new_target_column_texts = self.get_table_column_text("class", "target")
+            # if nothing found, we still count it as "pass"
+            if new_target_column_texts:
+                for text in new_target_column_texts:
+                    self.assertTrue(text.find(pattern), msg=("%s item doesn't exist " % pattern))
+            self.driver.find_element_by_css_selector("i.icon-remove").click()
+            target_column_texts = self.get_table_column_text("class", "target")
+            self.assertTrue(ori_target_column_texts == target_column_texts, msg=("builds changed after operations"))
+
+
+        ##############
+        #  CASE 903  #
+        ##############
+    def test_903(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # when opening a new page, "started_on" is not displayed by default
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # currently all the delay are for chrome driver -_-
+        self.browser_delay()
+        self.driver.find_element_by_id("started_on").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # step 4
+        items = ["Outcome", "Completed on", "Started on"]
+        for item in items:
+            try:
+                temp_element = self.find_element_by_text_in_table('otable', item)
+                # this is how we find "filter icon" in the same level as temp_element(where "a" means clickable, "i" means icon)
+                self.assertTrue(temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']"))
+            except Exception,e:
+                self.assertFalse(True, msg=(" %s cannot be found! %s" % (item, e)))
+                raise
+        # step 5-6
+        temp_element = self.find_element_by_link_text_in_table('otable', 'Outcome')
+        temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
+        self.browser_delay()
+        # the 2nd option, whatever it is
+        self.driver.find_element_by_xpath("(//input[@name='filter'])[2]").click()
+        # click "Apply" button
+        self.driver.find_element_by_xpath("//*[@id='filter_outcome']//*[text()='Apply']").click()
+        # save screen here
+        time.sleep(1)
+        self.save_screenshot(screenshot_type='selenium', append_name='step5')
+        temp_element = self.find_element_by_link_text_in_table('otable', 'Completed on')
+        temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
+        self.browser_delay()
+        self.driver.find_element_by_xpath("//*[@id='filter_completed_on']//*[text()='Apply']").click()
+        # save screen here to compare to previous one
+        # please note that for chrome driver, need a little break before saving
+        # screen here -_-
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step6')
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("core-image")
+        self.driver.find_element_by_id("search-button").click()
+
+
+        ##############
+        #  CASE 904  #
+        ##############
+    def test_904(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_partial_link_text("core-image").click()
+        self.driver.find_element_by_link_text("Tasks").click()
+        self.table_name = 'otable'
+        # This is how we find the "default" rows-number!
+        rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
+        print rows_displayed
+        self.assertTrue(self.get_table_element(self.table_name, rows_displayed), msg=("not enough rows displayed"))
+        self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1), \
+                         msg=("more rows displayed than expected"))
+        # Search text box background text is "Search tasks"
+        self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search tasks']"),\
+                        msg=("background text doesn't exist"))
+
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("busybox")
+        self.driver.find_element_by_id("search-button").click()
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step5')
+        self.driver.find_element_by_css_selector("i.icon-remove").click()
+        # Save screen here
+        self.save_screenshot(screenshot_type='selenium', append_name='step5_2')
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("cpu_used").click()
+        self.driver.find_element_by_id("disk_io").click()
+        self.driver.find_element_by_id("recipe_version").click()
+        self.driver.find_element_by_id("time_taken").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # The operation is the same as case901
+        # dict: {lint text name : actual class name}
+        table_head_dict = {'Order':'order', 'Recipe':'recipe_name', 'Task':'task_name', 'Executed':'executed', \
+                           'Outcome':'outcome', 'Cache attempt':'cache_attempt', 'Time (secs)':'time_taken', 'CPU usage':'cpu_used', \
+                           'Disk I/O (ms)':'disk_io'}
+        for key in table_head_dict:
+        # This is tricky here: we are doing so because there may be more than 1
+        # same-name link_text in one page. So we only find element inside the table
+            self.find_element_by_link_text_in_table(self.table_name, key).click()
+            column_list = self.get_table_column_text("class", table_head_dict[key])
+        # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
+        # the point is, after another click, it should be another order
+        # the first case is special:this means every item in column_list is the same, so
+        # after one click, either sequenced or inverted will be fine
+            if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
+                or (not column_list) :
+                self.find_element_by_link_text_in_table(self.table_name, key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list), \
+                                msg=("%s column not in any order" % key))
+            elif is_list_inverted(column_list):
+                self.find_element_by_link_text_in_table(self.table_name, key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
+            else:
+                self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
+                self.find_element_by_link_text_in_table(self.table_name, key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
+        # step 8-10
+        # filter dict: {link text name : filter table name in xpath}
+        filter_dict = {'Executed':'filter_executed', 'Outcome':'filter_outcome', 'Cache attempt':'filter_cache_attempt'}
+        for key in filter_dict:
+            temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
+            # find the filter icon besides it.
+            # And here we must have break (1 sec) to get the popup stuff
+            temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
+            self.browser_delay()
+            avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
+            for number in range(0, len(avail_options)):
+                avail_options[number].click()
+                self.browser_delay()
+                # click "Apply"
+                self.driver.find_element_by_xpath("//*[@id='" + filter_dict[key]  + "']//*[@type='submit']").click()
+                # insert screen capture here
+                self.browser_delay()
+                self.save_screenshot(screenshot_type='selenium', append_name='step8')
+                # after the last option was clicked, we don't need operation below anymore
+                if number < len(avail_options)-1:
+                     try:
+                        temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
+                        temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
+                        avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
+                     except:
+                        print "in exception"
+                        self.find_element_by_text("Show all tasks").click()
+#                        self.driver.find_element_by_xpath("//*[@id='searchform']/button[2]").click()
+                        temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
+                        temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
+                        avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
+                     self.browser_delay()
+        # step 11
+        for item in ['order', 'task_name', 'executed', 'outcome', 'recipe_name', 'recipe_version']:
+            try:
+                self.find_element_by_xpath_in_table(self.table_name, "./tbody/tr[1]/*[@class='" + item + "']/a").click()
+            except NoSuchElementException, e:
+            # let it go...
+                print 'no item in the colum' + item
+            # insert screen shot here
+            self.save_screenshot(screenshot_type='selenium', append_name='step11')
+            self.driver.back()
+        # step 12-14
+        # about test_dict: please refer to testcase 904 requirement step 12-14
+        test_dict = {
+            'Time':{
+                'class':'time_taken',
+                'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Time (secs)'],
+                'check_column_list':['cpu_used', 'cache_attempt', 'disk_io', 'order', 'recipe_version']
+            },
+            'CPU usage':{
+                'class':'cpu_used',
+                'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'CPU usage'],
+                'check_column_list':['cache_attempt', 'disk_io', 'order', 'recipe_version', 'time_taken']
+            },
+            'Disk I/O':{
+                'class':'disk_io',
+                'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Disk I/O (ms)'],
+                'check_column_list':['cpu_used', 'cache_attempt', 'order', 'recipe_version', 'time_taken']
+            }
+        }
+        for key in test_dict:
+            self.find_element_by_partial_link_text_in_table('nav', 'core-image').click()
+            self.find_element_by_link_text_in_table('nav', key).click()
+            head_list = self.get_table_head_text('otable')
+            for item in test_dict[key]['check_head_list']:
+                self.assertTrue(item in head_list, msg=("%s not in head row" % item))
+            column_list = self.get_table_column_text('class', test_dict[key]['class'])
+            self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
+
+            self.driver.find_element_by_id("edit-columns-button").click()
+            for item2 in test_dict[key]['check_column_list']:
+                self.driver.find_element_by_id(item2).click()
+            self.driver.find_element_by_id("edit-columns-button").click()
+            # TBD: save screen here
+
+
+        ##############
+        #  CASE 906  #
+        ##############
+    def test_906(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'Packages').click()
+        # find "bash" in first column (Packages)
+        self.driver.find_element_by_xpath("//*[@id='otable']//td[1]//*[text()='bash']").click()
+        # save sceen here to observe...
+        # step 6
+        self.driver.find_element_by_partial_link_text("Generated files").click()
+        head_list = self.get_table_head_text('otable')
+        for item in ['File', 'Size']:
+            self.assertTrue(item in head_list, msg=("%s not in head row" % item))
+        c_list = self.get_table_column_text('class', 'path')
+        self.assertTrue(is_list_sequenced(c_list), msg=("column not in order"))
+        # step 7
+        self.driver.find_element_by_partial_link_text("Runtime dependencies").click()
+        # save sceen here to observe...
+        # note that here table name is not 'otable'
+        head_list = self.get_table_head_text('dependencies')
+        for item in ['Package', 'Version', 'Size']:
+            self.assertTrue(item in head_list, msg=("%s not in head row" % item))
+        c_list = self.get_table_column_text_by_column_number('dependencies', 1)
+        self.assertTrue(is_list_sequenced(c_list), msg=("list not in order"))
+        texts = ['Size', 'License', 'Recipe', 'Recipe version', 'Layer', \
+                     'Layer commit']
+        self.failUnless(self.is_text_present(texts))
+
+
+        ##############
+        #  CASE 910  #
+        ##############
+    def test_910(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        image_type="core-image-minimal"
+        test_package1="busybox"
+        test_package2="lib"
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text(image_type).click()
+        self.driver.find_element_by_link_text("Recipes").click()
+        self.save_screenshot(screenshot_type='selenium', append_name='step3')
+
+        self.table_name = 'otable'
+        # This is how we find the "default" rows-number!
+        rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
+        print rows_displayed
+        self.assertTrue(self.get_table_element(self.table_name, rows_displayed))
+        self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1))
+
+        # Check the default table is sorted by Recipe
+        tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
+        print tasks_column_count
+        default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
+        #print default_column_list
+
+        self.assertTrue(is_list_sequenced(default_column_list))
+
+        # Search text box background text is "Search recipes"
+        self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
+
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys(test_package1)
+        self.driver.find_element_by_id("search-button").click()
+        # Save screen here
+        self.save_screenshot(screenshot_type='selenium', append_name='step4')
+        self.driver.find_element_by_css_selector("i.icon-remove").click()
+        self.save_screenshot(screenshot_type='selenium', append_name='step4_2')
+
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("depends_on").click()
+        self.driver.find_element_by_id("layer_version__branch").click()
+        self.driver.find_element_by_id("layer_version__layer__commit").click()
+        self.driver.find_element_by_id("depends_by").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+
+        self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
+        # Check the inverted table by Recipe
+        # Recipe doesn't have class name
+        #inverted_tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
+        #print inverted_tasks_column_count
+        #inverted_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
+        #print inverted_column_list
+
+        #self.driver.find_element_by_partial_link_text("zlib").click()
+        #self.driver.back()
+        #self.assertTrue(is_list_inverted(inverted_column_list))
+        #self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
+
+        table_head_dict = {'Recipe':'recipe__name', 'Recipe file':'recipe_file', 'Section':'recipe_section', \
+                'License':'recipe_license', 'Layer':'layer_version__layer__name', \
+                'Layer branch':'layer_version__branch'}
+        for key in table_head_dict:
+            self.find_element_by_link_text_in_table(self.table_name, key).click()
+            column_list = self.get_table_column_text("class", table_head_dict[key])
+            if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
+                    or (not column_list) :
+                self.find_element_by_link_text_in_table(self.table_name, key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list))
+                self.driver.find_element_by_partial_link_text("acl").click()
+                self.driver.back()
+                self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list))
+                # Search text box background text is "Search recipes"
+                self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
+                self.driver.find_element_by_id("search").clear()
+                self.driver.find_element_by_id("search").send_keys(test_package2)
+                self.driver.find_element_by_id("search-button").click()
+                column_search_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_search_list) or is_list_inverted(column_search_list))
+                self.driver.find_element_by_css_selector("i.icon-remove").click()
+            elif is_list_inverted(column_list):
+                self.find_element_by_link_text_in_table(self.table_name, key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_list))
+                self.driver.find_element_by_partial_link_text("acl").click()
+                self.driver.back()
+                self.assertTrue(is_list_sequenced(column_list))
+                # Search text box background text is "Search recipes"
+                self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
+                self.driver.find_element_by_id("search").clear()
+                self.driver.find_element_by_id("search").send_keys(test_package2)
+                self.driver.find_element_by_id("search-button").click()
+                column_search_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_sequenced(column_search_list))
+                self.driver.find_element_by_css_selector("i.icon-remove").click()
+            else:
+                self.assertTrue(is_list_sequenced(column_list),  msg=("list %s not sequenced" % key))
+                self.find_element_by_link_text_in_table(self.table_name, key).click()
+                column_list = self.get_table_column_text("class", table_head_dict[key])
+                self.assertTrue(is_list_inverted(column_list))
+                try:
+                    self.driver.find_element_by_partial_link_text("acl").click()
+                except:
+                    self.driver.find_element_by_partial_link_text("zlib").click()
+                self.driver.back()
+                self.assertTrue(is_list_inverted(column_list))
+                # Search text box background text is "Search recipes"
+                self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
+                self.driver.find_element_by_id("search").clear()
+                self.driver.find_element_by_id("search").send_keys(test_package2)
+                self.driver.find_element_by_id("search-button").click()
+                column_search_list = self.get_table_column_text("class", table_head_dict[key])
+                #print column_search_list
+                self.assertTrue(is_list_inverted(column_search_list))
+                self.driver.find_element_by_css_selector("i.icon-remove").click()
+
+        # Bug 5919
+        for key in table_head_dict:
+            print key
+            self.find_element_by_link_text_in_table(self.table_name, key).click()
+            self.driver.find_element_by_id("edit-columns-button").click()
+            self.driver.find_element_by_id(table_head_dict[key]).click()
+            self.driver.find_element_by_id("edit-columns-button").click()
+            self.browser_delay()
+            # After hide the column, the default table should be sorted by Recipe
+            tasks_column_count = len(self.driver.find_elements_by_partial_link_text("acl"))
+            #print tasks_column_count
+            default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
+            #print default_column_list
+            self.assertTrue(is_list_sequenced(default_column_list))
+
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("recipe_file").click()
+        self.driver.find_element_by_id("recipe_section").click()
+        self.driver.find_element_by_id("recipe_license").click()
+        self.driver.find_element_by_id("layer_version__layer__name").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+
+
+        ##############
+        #  CASE 911  #
+        ##############
+    def test_911(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'Recipes').click()
+        # step 3-5
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("lib")
+        self.driver.find_element_by_id("search-button").click()
+        # save screen here for observation
+        self.save_screenshot(screenshot_type='selenium', append_name='step5')
+        # step 6
+        self.driver.find_element_by_css_selector("i.icon-remove").click()
+        self.driver.find_element_by_id("search").clear()
+        # we deliberately want "no result" here
+        self.driver.find_element_by_id("search").send_keys("no such input")
+        self.driver.find_element_by_id("search-button").click()
+        try:
+            self.find_element_by_text("Show all recipes").click()
+        except:
+            self.fail(msg='Could not identify blank page elements')
+
+        ##############
+        #  CASE 912  #
+        ##############
+    def test_912(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'Recipes').click()
+        # step 3
+        head_list = self.get_table_head_text('otable')
+        for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
+            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("depends_on").click()
+        self.driver.find_element_by_id("layer_version__branch").click()
+        self.driver.find_element_by_id("layer_version__layer__commit").click()
+        self.driver.find_element_by_id("depends_by").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # check if columns selected above is shown
+        check_list = ['Dependencies', 'Layer branch', 'Layer commit', 'Reverse dependencies']
+        head_list = self.get_table_head_text('otable')
+        time.sleep(2)
+        print head_list
+        for item in check_list:
+            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
+        # un-check 'em all
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("depends_on").click()
+        self.driver.find_element_by_id("layer_version__branch").click()
+        self.driver.find_element_by_id("layer_version__layer__commit").click()
+        self.driver.find_element_by_id("depends_by").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # don't exist any more
+        head_list = self.get_table_head_text('otable')
+        for item in check_list:
+            self.assertFalse(item in head_list, msg=("item %s should not be in head row" % item))
+
+
+        ##############
+        #  CASE 913  #
+        ##############
+    def test_913(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'Recipes').click()
+        # step 3
+        head_list = self.get_table_head_text('otable')
+        for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
+            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
+        # step 4
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # save screen
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step4')
+        self.driver.find_element_by_id("edit-columns-button").click()
+
+
+        ##############
+        #  CASE 914  #
+        ##############
+    def test_914(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        image_type="core-image-minimal"
+        test_package1="busybox"
+        test_package2="gdbm"
+        test_package3="gettext-native"
+        driver = self.driver
+        driver.maximize_window()
+        driver.get(self.base_url)
+        driver.find_element_by_link_text(image_type).click()
+        driver.find_element_by_link_text("Recipes").click()
+        driver.find_element_by_link_text(test_package1).click()
+
+        self.table_name = 'information'
+
+        tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr/td[1]"))
+        tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr[1]/td"))
+        print 'rows: '+str(tasks_row_count)
+        print 'columns: '+str(tasks_column_count)
+
+        Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
+        print ("Tasks_column=", Tasks_column)
+
+        key_tasks=["do_fetch", "do_unpack",  "do_patch", "do_configure", "do_compile", "do_install", "do_package", "do_build"]
+        i = 0
+        while i < len(key_tasks):
+            if key_tasks[i] not in Tasks_column:
+                print ("Error! Missing key task: %s" % key_tasks[i])
+            else:
+                print ("%s is in tasks" % key_tasks[i])
+            i = i + 1
+
+        if Tasks_column.index(key_tasks[0]) != 0:
+            print ("Error! %s is not in the right position" % key_tasks[0])
+        else:
+            print ("%s is in right position" % key_tasks[0])
+
+        if Tasks_column[-1] != key_tasks[-1]:
+            print ("Error! %s is not in the right position" % key_tasks[-1])
+        else:
+            print ("%s is in right position" % key_tasks[-1])
+
+        driver.find_element_by_partial_link_text("Packages (").click()
+        packages_name = driver.find_element_by_partial_link_text("Packages (").text
+        print packages_name
+        packages_num = int(filter(str.isdigit, repr(packages_name)))
+        print packages_num
+
+        #switch the table to show more than 10 rows at a time
+        self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").click()
+        Select(driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select")).select_by_value('150')
+        self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").send_keys(Keys.ENTER)
+
+        packages_row_count = len(driver.find_elements_by_xpath("//*[@id='otable']/tbody/tr/td[1]"))
+        print packages_row_count
+
+        if packages_num != packages_row_count:
+            print ("Error! The packages number is not correct")
+        else:
+            print ("The packages number is correct")
+
+        driver.find_element_by_partial_link_text("Build dependencies (").click()
+        depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
+        print depends_name
+        depends_num = int(filter(str.isdigit, repr(depends_name)))
+        print depends_num
+
+        if depends_num == 0:
+            depends_message = repr(driver.find_element_by_css_selector("div.alert.alert-info").text)
+            print depends_message
+            if depends_message.find("has no build dependencies.") < 0:
+                print ("Error! The message isn't expected.")
+            else:
+                print ("The message is expected")
+        else:
+            depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]"))
+            print depends_row_count
+            if depends_num != depends_row_count:
+                print ("Error! The dependent packages number is not correct")
+            else:
+                print ("The dependent packages number is correct")
+
+        driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
+        rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
+        print rdepends_name
+        rdepends_num = int(filter(str.isdigit, repr(rdepends_name)))
+        print rdepends_num
+
+        if rdepends_num == 0:
+            rdepends_message = repr(driver.find_element_by_css_selector("#brought-in-by > div.alert.alert-info").text)
+            print rdepends_message
+            if rdepends_message.find("has no reverse build dependencies.") < 0:
+                print ("Error! The message isn't expected.")
+            else:
+                print ("The message is expected")
+        else:
+            print ("The reverse dependent packages number is correct")
+
+        driver.find_element_by_link_text("Recipes").click()
+        driver.find_element_by_link_text(test_package2).click()
+        driver.find_element_by_partial_link_text("Packages (").click()
+        driver.find_element_by_partial_link_text("Build dependencies (").click()
+        driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
+
+
+        driver.find_element_by_link_text("Recipes").click()
+        driver.find_element_by_link_text(test_package3).click()
+
+        native_tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr/td[1]"))
+        native_tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr[1]/td"))
+        print native_tasks_row_count
+        print native_tasks_column_count
+
+        Native_Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
+        print ("Native_Tasks_column=", Native_Tasks_column)
+
+        native_key_tasks=["do_fetch", "do_unpack",  "do_patch", "do_configure", "do_compile", "do_install", "do_build"]
+        i = 0
+        while i < len(native_key_tasks):
+            if native_key_tasks[i] not in Native_Tasks_column:
+                print ("Error! Missing key task: %s" % native_key_tasks[i])
+            else:
+                print ("%s is in tasks" % native_key_tasks[i])
+            i = i + 1
+
+        if Native_Tasks_column.index(native_key_tasks[0]) != 0:
+            print ("Error! %s is not in the right position" % native_key_tasks[0])
+        else:
+            print ("%s is in right position" % native_key_tasks[0])
+
+        if Native_Tasks_column[-1] != native_key_tasks[-1]:
+            print ("Error! %s is not in the right position" % native_key_tasks[-1])
+        else:
+            print ("%s is in right position" % native_key_tasks[-1])
+
+        driver.find_element_by_partial_link_text("Packages (").click()
+        native_packages_name = driver.find_element_by_partial_link_text("Packages (").text
+        print native_packages_name
+        native_packages_num = int(filter(str.isdigit, repr(native_packages_name)))
+        print native_packages_num
+
+        if native_packages_num != 0:
+            print ("Error! Native task shouldn't have any packages.")
+        else:
+            native_package_message = repr(driver.find_element_by_css_selector("#packages-built > div.alert.alert-info").text)
+            print native_package_message
+            if native_package_message.find("does not build any packages.") < 0:
+                print ("Error! The message for native task isn't expected.")
+            else:
+                print ("The message for native task is expected.")
+
+        driver.find_element_by_partial_link_text("Build dependencies (").click()
+        native_depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
+        print native_depends_name
+        native_depends_num = int(filter(str.isdigit, repr(native_depends_name)))
+        print native_depends_num
+
+        native_depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]"))
+        print native_depends_row_count
+
+        if native_depends_num != native_depends_row_count:
+            print ("Error! The dependent packages number is not correct")
+        else:
+            print ("The dependent packages number is correct")
+
+        driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
+        native_rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
+        print native_rdepends_name
+        native_rdepends_num = int(filter(str.isdigit, repr(native_rdepends_name)))
+        print native_rdepends_num
+
+        native_rdepends_row_count = len(driver.find_elements_by_xpath("//*[@id='brought-in-by']/table/tbody/tr/td[1]"))
+        print native_rdepends_row_count
+
+        if native_rdepends_num != native_rdepends_row_count:
+            print ("Error! The reverse dependent packages number is not correct")
+        else:
+            print ("The reverse dependent packages number is correct")
+
+        driver.find_element_by_link_text("Recipes").click()
+
+
+        ##############
+        #  CASE 915  #
+        ##############
+    def test_915(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        # step 3
+        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
+        self.driver.find_element_by_link_text("BitBake variables").click()
+        # step 4
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("lib")
+        self.driver.find_element_by_id("search-button").click()
+        # save screen to see result
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step4')
+        # step 5
+        self.driver.find_element_by_css_selector("i.icon-remove").click()
+        head_list = self.get_table_head_text('otable')
+        print head_list
+        print len(head_list)
+        self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
+                        msg=("head row contents wrong"))
+        # step 8
+        # search other string. and click "Variable" to re-sort, check if table
+        # head is still the same
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("poky")
+        self.driver.find_element_by_id("search-button").click()
+        self.find_element_by_link_text_in_table('otable', 'Variable').click()
+        head_list = self.get_table_head_text('otable')
+        self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
+                        msg=("head row contents wrong"))
+        self.find_element_by_link_text_in_table('otable', 'Variable').click()
+        head_list = self.get_table_head_text('otable')
+        self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
+                        msg=("head row contents wrong"))
+
+
+        ##############
+        #  CASE 916  #
+        ##############
+    def test_916(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        # step 2-3
+        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
+        self.driver.find_element_by_link_text("BitBake variables").click()
+        variable_list = self.get_table_column_text('class', 'variable_name')
+        self.assertTrue(is_list_sequenced(variable_list), msg=("list not in order"))
+        # step 4
+        self.find_element_by_link_text_in_table('otable', 'Variable').click()
+        variable_list = self.get_table_column_text('class', 'variable_name')
+        self.assertTrue(is_list_inverted(variable_list), msg=("list not inverted"))
+        self.find_element_by_link_text_in_table('otable', 'Variable').click()
+        # step 5
+        # searching won't change the sequentiality
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("lib")
+        self.driver.find_element_by_id("search-button").click()
+        variable_list = self.get_table_column_text('class', 'variable_name')
+        self.assertTrue(is_list_sequenced(variable_list), msg=("list not in order"))
+
+
+        ##############
+        #  CASE 923  #
+        ##############
+    def test_923(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # Step 2
+        # default sequence in "Completed on" column is inverted
+        c_list = self.get_table_column_text('class', 'completed_on')
+        self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
+        # step 3
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("started_on").click()
+        self.driver.find_element_by_id("time").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        head_list = self.get_table_head_text('otable')
+        for item in ['Outcome', 'Recipe', 'Machine', 'Started on', 'Completed on', 'Failed tasks', 'Errors', 'Warnings', 'Time', "Image files", "Project"]:
+            self.failUnless(item in head_list, msg=item+' is missing from table head.')
+
+
+        ##############
+        #  CASE 924  #
+        ##############
+    def test_924(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # Please refer to case 924 requirement
+        # default sequence in "Completed on" column is inverted
+        c_list = self.get_table_column_text('class', 'completed_on')
+        self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
+        # Step 4
+        # click Errors , order in "Completed on" should be disturbed. Then hide
+        # error column to check if order in "Completed on" can be restored
+#THIS TEST IS NO LONGER VALID DUE TO DESIGN CHANGES. LEAVING IN PENDING UPDATES TO DESIGN
+        #self.find_element_by_link_text_in_table('otable', 'Errors').click()
+        #self.driver.find_element_by_id("edit-columns-button").click()
+        #self.driver.find_element_by_id("errors_no").click()
+        #self.driver.find_element_by_id("edit-columns-button").click()
+        # Note: without time.sleep here, there'll be unpredictable error..TBD
+        time.sleep(1)
+        c_list = self.get_table_column_text('class', 'completed_on')
+        self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
+
+
+        ##############
+        #  CASE 940  #
+        ##############
+    def test_940(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        # Step 2-3
+        self.find_element_by_link_text_in_table('nav', 'Packages').click()
+        check_head_list = ['Package', 'Package version', 'Size', 'Recipe']
+        head_list = self.get_table_head_text('otable')
+        self.assertTrue(head_list == check_head_list, msg=("head row not as expected"))
+        # Step 4
+        # pulldown menu
+        option_ids = ['recipe__layer_version__layer__name', 'recipe__layer_version__branch', \
+                      'recipe__layer_version__layer__commit', 'license', 'recipe__version']
+        self.driver.find_element_by_id("edit-columns-button").click()
+        for item in option_ids:
+            if not self.driver.find_element_by_id(item).is_selected():
+                self.driver.find_element_by_id(item).click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # save screen here to observe that 'Package' and 'Package version' is
+        # not selectable
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step4')
+
+
+        ##############
+        #  CASE 941  #
+        ##############
+    def test_941(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        # Step 2-3
+        self.find_element_by_link_text_in_table('nav', 'Packages').click()
+        # column -- Package
+        column_list = self.get_table_column_text_by_column_number('otable', 1)
+        self.assertTrue(is_list_sequenced(column_list), msg=("list not in order"))
+        self.find_element_by_link_text_in_table('otable', 'Size').click()
+
+
+        ##############
+        #  CASE 942  #
+        ##############
+    def test_942(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.driver.find_element_by_link_text("Packages").click()
+        #get initial table header
+        head_list = self.get_table_head_text('otable')
+        #remove the Recipe column from table header
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("recipe__name").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        #get modified table header
+        new_head = self.get_table_head_text('otable')
+        self.assertTrue(head_list > new_head)
+
+        ##############
+        #  CASE 943  #
+        ##############
+    def test_943(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.driver.find_element_by_link_text("Packages").click()
+        #search for the "bash" package -> this should definitely be present
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("bash")
+        self.driver.find_element_by_id("search-button").click()
+        #check for the search result message "XX packages found"
+        self.assertTrue(self.is_text_present("packages found"), msg=("no packages found text"))
+
+
+        ##############
+        #  CASE 944  #
+        ##############
+    def test_944(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        # step 1: test Recipes page stuff
+        self.driver.find_element_by_link_text("Recipes").click()
+        # for these 3 items, default status is not-checked
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("layer_version__branch").click()
+        self.driver.find_element_by_id("layer_version__layer__commit").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # otable is the recipes table here
+        otable_head_text = self.get_table_head_text('otable')
+        for item in ["Layer", "Layer branch", "Layer commit"]:
+            self.failIf(item not in otable_head_text, msg=item+' not in table head.')
+        # click the fist recipe, whatever it is
+        self.get_table_element("otable", 1, 1).click()
+        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit", "Recipe file"]), \
+                        msg=("text not in web page"))
+
+        # step 2: test Packages page stuff. almost same as above
+        self.driver.back()
+        self.browser_delay()
+        self.driver.find_element_by_link_text("Packages").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("recipe__layer_version__layer__name").click()
+        self.driver.find_element_by_id("recipe__layer_version__branch").click()
+        self.driver.find_element_by_id("recipe__layer_version__layer__commit").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        otable_head_text = self.get_table_head_text("otable")
+        for item in ["Layer", "Layer branch", "Layer commit"]:
+            self.assertFalse(item not in otable_head_text, msg=("item %s should be in head row" % item))
+        # click the fist recipe, whatever it is
+        self.get_table_element("otable", 1, 1).click()
+        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
+                        msg=("text not in web page"))
+
+        # step 3: test Packages core-image-minimal(images) stuff. almost same as above. Note when future element-id changes...
+        self.driver.back()
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        self.driver.find_element_by_id("layer_name").click()
+        self.driver.find_element_by_id("layer_branch").click()
+        self.driver.find_element_by_id("layer_commit").click()
+        self.driver.find_element_by_id("edit-columns-button").click()
+        otable_head_text = self.get_table_head_text("otable")
+        for item in ["Layer", "Layer branch", "Layer commit"]:
+            self.assertFalse(item not in otable_head_text, msg=("item %s should be in head row" % item))
+        # click the fist recipe, whatever it is
+        self.get_table_element("otable", 1, 1).click()
+        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
+                        msg=("text not in web page"))
+
+        # step 4: check Configuration page
+        self.driver.back()
+        self.driver.find_element_by_link_text("Configuration").click()
+        otable_head_text = self.get_table_head_text()
+        self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
+                        msg=("text not in web page"))
+
+
+        ##############
+        #  CASE 945  #
+        ##############
+    def test_945(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        for item in ["Packages", "Recipes", "Tasks"]:
+            self.driver.get(self.base_url)
+            self.driver.find_element_by_link_text("core-image-minimal").click()
+            self.driver.find_element_by_link_text(items).click()
+
+            # this may be page specific. If future page content changes, try to replace it with new xpath
+            xpath_showrows = "/html/body/div[4]/div/div/div[2]/div[2]/div[2]/div/div/div[2]/select"
+            xpath_table = "html/body/div[4]/div/div/div[2]/div[2]/table/tbody"#"id=('otable')/tbody"
+            self.driver.find_element_by_xpath(xpath_showrows).click()
+            rows_displayed = int(self.driver.find_element_by_xpath(xpath_showrows + "/option[2]").text)
+
+            # not sure if this is a Selenium Select bug: If page is not refreshed here, "select(by visible text)" operation will go back to 100-row page
+            # Sure we can use driver.get(url) to refresh page, but since page will vary, we use click link text here
+            self.driver.find_element_by_link_text(items).click()
+            Select(self.driver.find_element_by_css_selector("select.pagesize")).select_by_visible_text(str(rows_displayed))
+            self.failUnless(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed) +"]"))
+            self.failIf(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed+1) +"]"))
+
+            # click 1st package, then go back to check if it's still those rows shown.
+            self.driver.find_element_by_xpath(xpath_otable + "/tr[1]/td[1]/a").click()
+            time.sleep(3)
+            self.driver.find_element_by_link_text(item).click()
+            self.assertTrue(self.is_element_present(By.XPATH, xpath_otable + "/tr[" + str(option_tobeselected) +"]"),\
+                            msg=("Row %d should exist" %option_tobeselected))
+            self.assertFalse(self.is_element_present(By.XPATH, xpath_otable + "/tr[" + str(option_tobeselected+1) +"]"),\
+                            msg=("Row %d should not exist" %(option_tobeselected+1)))
+
+
+
+        ##############
+        #  CASE 946  #
+        ##############
+    def test_946(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.driver.find_element_by_link_text("Configuration").click()
+        # step 3-4
+        check_list = ["Summary", "BitBake variables"]
+        for item in check_list:
+            if not self.is_element_present(how=By.LINK_TEXT, what=item):
+                self.log.error("%s not found" %item)
+        if not self.is_text_present(['Layers', 'Layer', 'Layer branch', 'Layer commit']):
+            self.log.error("text not found")
+        # step 5
+        self.driver.find_element_by_link_text("BitBake variables").click()
+        if not self.is_text_present(['Variable', 'Value', 'Set in file', 'Description']):
+            self.log.error("text not found")
+        # This may be unstable because it's page-specific
+        # step 6: this is how we find filter beside "Set in file"
+        temp_element = self.find_element_by_text_in_table('otable', "Set in file")
+        temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
+        self.browser_delay()
+        self.driver.find_element_by_xpath("(//input[@name='filter'])[3]").click()
+        btns = self.driver.find_elements_by_css_selector("button.btn.btn-primary")
+        for btn in btns:
+            try:
+                btn.click()
+                break
+            except:
+                pass
+        # save screen here
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step6')
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # save screen here
+        # step 7
+        # we should manually check the step 6-8 result using screenshot
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step7')
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # step 9
+        # click the 1st item, no matter what it is
+        self.driver.find_element_by_xpath("//*[@id='otable']/tbody/tr[1]/td[1]/a").click()
+        # give it 1 sec so the pop-up becomes the "active_element"
+        time.sleep(1)
+        element = self.driver.switch_to.active_element
+        check_list = ['Order', 'Configuration file', 'Operation', 'Line number']
+        for item in check_list:
+            if item not in element.text:
+                self.log.error("%s not found" %item)
+        # any better way to close this pop-up? ... TBD
+        element.find_element_by_class_name("close").click()
+        # step 10 : need to manually check "Yocto Manual" in saved screen
+        self.driver.find_element_by_css_selector("i.icon-share.get-info").click()
+        # save screen here
+        time.sleep(5)
+        self.save_screenshot(screenshot_type='native', append_name='step10')
+
+
+        ##############
+        #  CASE 947  #
+        ##############
+    def test_947(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
+        # step 2
+        self.driver.find_element_by_link_text("BitBake variables").click()
+        # step 3
+        def xpath_option(column_name):
+            # return xpath of options under "Edit columns" button
+            return self.shortest_xpath('id', 'navTab') + self.shortest_xpath('id', 'editcol') \
+                + self.shortest_xpath('id', column_name)
+        self.driver.find_element_by_id('edit-columns-button').click()
+        # by default, option "Description" and "Set in file" were checked
+        self.driver.find_element_by_xpath(xpath_option('description')).click()
+        self.driver.find_element_by_xpath(xpath_option('file')).click()
+        self.driver.find_element_by_id('edit-columns-button').click()
+        check_list = ['Description', 'Set in file']
+        head_list = self.get_table_head_text('otable')
+        for item in check_list:
+            self.assertFalse(item in head_list, msg=("item %s should not be in head row" % item))
+        # check these 2 options and verify again
+        self.driver.find_element_by_id('edit-columns-button').click()
+        self.driver.find_element_by_xpath(xpath_option('description')).click()
+        self.driver.find_element_by_xpath(xpath_option('file')).click()
+        self.driver.find_element_by_id('edit-columns-button').click()
+        head_list = self.get_table_head_text('otable')
+        for item in check_list:
+            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
+
+
+        ##############
+        #  CASE 948  #
+        ##############
+    def test_948(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'Configuration').click()
+        self.driver.find_element_by_link_text("BitBake variables").click()
+        #get number of variables visible by default
+        number_before_search = self.driver.find_element_by_class_name('page-header').text
+        # search for a while...
+        self.driver.find_element_by_id("search").clear()
+        self.driver.find_element_by_id("search").send_keys("BB")
+        self.driver.find_element_by_id("search-button").click()
+        #get number of variables visible after search
+        number_after_search = self.driver.find_element_by_class_name('page-header').text
+        self.assertTrue(number_before_search > number_after_search, msg=("items should be less after search"))
+
+
+        ##############
+        #  CASE 949  #
+        ##############
+    def test_949(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        self.find_element_by_link_text_in_table('nav', 'core-image-minimal').click()
+        # step 3
+        try:
+            self.driver.find_element_by_partial_link_text("Packages included")
+            self.driver.find_element_by_partial_link_text("Directory structure")
+        except Exception,e:
+            self.log.error(e)
+            self.assertFalse(True)
+        # step 4
+        head_list = self.get_table_head_text('otable')
+        for item in ['Package', 'Package version', 'Size', 'Dependencies', 'Reverse dependencies', 'Recipe']:
+            self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
+        # step 5-6
+        self.driver.find_element_by_id("edit-columns-button").click()
+        selectable_class = 'checkbox'
+        # minimum-table : means unselectable items
+        unselectable_class = 'checkbox muted'
+        selectable_check_list = ['Dependencies', 'Layer', 'Layer branch', 'Layer commit', \
+                                 'License', 'Recipe', 'Recipe version', 'Reverse dependencies', \
+                                 'Size', 'Size over total (%)']
+        unselectable_check_list = ['Package', 'Package version']
+        selectable_list = list()
+        unselectable_list = list()
+        selectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + selectable_class + "']")
+        unselectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + unselectable_class + "']")
+        for element in selectable_elements:
+            selectable_list.append(element.text)
+        for element in unselectable_elements:
+            unselectable_list.append(element.text)
+        # check them
+        for item in selectable_check_list:
+            self.assertTrue(item in selectable_list, msg=("%s not found in dropdown menu" % item))
+        for item in unselectable_check_list:
+            self.assertTrue(item in unselectable_list, msg=("%s not found in dropdown menu" % item))
+        self.driver.find_element_by_id("edit-columns-button").click()
+        # step 7
+        self.driver.find_element_by_partial_link_text("Directory structure").click()
+        head_list = self.get_table_head_text('dirtable')
+        for item in ['Directory / File', 'Symbolic link to', 'Source package', 'Size', 'Permissions', 'Owner', 'Group']:
+            self.assertTrue(item in head_list, msg=("%s not found in Directory structure table head" % item))
+
+        ##############
+        #  CASE 950  #
+        ##############
+    def test_950(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # step3&4: so far we're not sure if there's "successful build" or "failed
+        # build".If either of them doesn't exist, we can still go on other steps
+        check_list = ['Configuration', 'Tasks', 'Recipes', 'Packages', 'Time', 'CPU usage', 'Disk I/O']
+        has_successful_build = 1
+        has_failed_build = 1
+        try:
+            pass_icon = self.driver.find_element_by_xpath("//*[@class='icon-ok-sign success']")
+        except Exception:
+            self.log.info("no successful build exists")
+            has_successful_build = 0
+            pass
+        if has_successful_build:
+            pass_icon.click()
+            # save screen here to check if it matches requirement.
+            self.browser_delay()
+            self.save_screenshot(screenshot_type='selenium', append_name='step3_1')
+            for item in check_list:
+                try:
+                    self.find_element_by_link_text_in_table('nav', item)
+                except Exception:
+                    self.assertFalse(True, msg=("link  %s cannot be found in the page" % item))
+            # step 6
+            check_list_2 = ['Packages included', 'Total package size', \
+                      'License manifest', 'Image files']
+            self.assertTrue(self.is_text_present(check_list_2), msg=("text not in web page"))
+            self.driver.back()
+        try:
+            fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
+        except Exception:
+            has_failed_build = 0
+            self.log.info("no failed build exists")
+            pass
+        if has_failed_build:
+            fail_icon.click()
+            # save screen here to check if it matches requirement.
+            self.browser_delay()
+            self.save_screenshot(screenshot_type='selenium', append_name='step3_2')
+            for item in check_list:
+                try:
+                    self.find_element_by_link_text_in_table('nav', item)
+                except Exception:
+                    self.assertFalse(True, msg=("link  %s cannot be found in the page" % item))
+            # step 7 involved
+            check_list_3 = ['Machine', 'Distro', 'Layers', 'Total number of tasks', 'Tasks executed', \
+                      'Tasks not executed', 'Reuse', 'Recipes built', 'Packages built']
+            self.assertTrue(self.is_text_present(check_list_3), msg=("text not in web page"))
+            self.driver.back()
+
+
+        ##############
+        #  CASE 951  #
+        ##############
+    def test_951(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # currently test case itself isn't responsible for creating "1 successful and
+        # 1 failed build"
+        has_successful_build = 1
+        has_failed_build = 1
+        try:
+            fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
+        except Exception:
+            has_failed_build = 0
+            self.log.info("no failed build exists")
+            pass
+        # if there's failed build, we can proceed
+        if has_failed_build:
+            self.driver.find_element_by_partial_link_text("error").click()
+            self.driver.back()
+        # not sure if there "must be" some warnings, so here save a screen
+        self.browser_delay()
+        self.save_screenshot(screenshot_type='selenium', append_name='step4')
+
+
+        ##############
+        #  CASE 955  #
+        ##############
+    def test_955(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.log.info(" You should manually create all images before test starts!")
+        # So far the case itself is not responsable for creating all sorts of images.
+        # So assuming they are already there
+        # step 2
+        self.driver.find_element_by_link_text("core-image-minimal").click()
+        # save screen here to see the page component
+
+
+        ##############
+        #  CASE 956  #
+        ##############
+    def test_956(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        # step 2-3 need to run manually
+        self.log.info("step 2-3: checking the help message when you hover on help icon of target,\
+                       tasks, recipes, packages need to run manually")
+        self.driver.find_element_by_partial_link_text("Manual").click()
+        if not self.is_text_present("Manual"):
+            self.log.error("please check [Toaster manual] link on page")
+            self.failIf(True)
+
+####################################################################################################
+# Starting backend tests ###########################################################################
+####################################################################################################
+
+        ##############
+        #  CASE 1066 #
+        ##############
+    def test_1066(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select count(name) from orm_project a, auth_user b where a.user_id = b.id and b.username='_anonuser';"
+        cursor.execute(query)
+        data = cursor.fetchone()
+        self.failUnless(data >= 1)
+
+
+        ##############
+        #  CASE 1071 #
+        ##############
+    def test_1071(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select name from orm_release;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        for i in range(0,4):
+            data[i] = data[i][0]
+        data.sort()
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_data = []
+        for i in range (0,4):
+            json_data.append(json_parse['releases'][i]['name'])
+        json_data.sort()
+        print json_data
+        self.failUnless(data == json_data)
+
+        ##############
+        #  CASE 1072 #
+        ##############
+    def test_1072(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select value from orm_toastersetting where name like 'DEFCONF%';"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        for i in range(0,6):
+            data[i] = data[i][0]
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_data=json_parse['config']
+        json_data = json_data.values()
+        print json_data
+        self.failUnless(data == json_data)
+
+
+        ##############
+        #  CASE 1074 #
+        ##############
+    def test_1074(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select name from orm_layersource;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        for i in range(0,3):
+            data[i] = data[i][0]
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_data = []
+        for i in range(0,3):
+            json_data.append(json_parse['layersources'][i]['name'])
+        print json_data
+        self.failUnless(set(data) == set(json_data))
+
+        ##############
+        #  CASE 1075 #
+        ##############
+    def test_1075(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select value from orm_toastersetting where name like 'DEFAULT_RELEASE';"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        data = data[0][0]
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_data = json_parse['defaultrelease']
+        print json_data
+        self.failUnless(set(data) == set(json_data))
+
+        ##############
+        #  CASE 1076 #
+        ##############
+    def test_1076(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+
+        print 'Checking branches for "Local Yocto Project"'
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select name from orm_branch where layer_source_id=1;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        lenght = len(data)
+        try:
+            for i in range(0,lenght):
+                data[i] = data[i][0]
+        except:
+            pass
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_location = json_parse['layersources'][0]['name']
+        print json_location
+        json_data = json_parse['layersources'][0]['branches']
+        print json_data
+        self.failUnless(set(data) == set(json_data))
+
+        print 'Checking branches for "OpenEmbedded"'
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select name from orm_branch where layer_source_id=3;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        lenght = len(data)
+        for i in range(0,lenght):
+            data[i] = data[i][0]
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_location = json_parse['layersources'][1]['name']
+        print json_location
+        json_data = json_parse['layersources'][1]['branches']
+        print json_data
+        self.failUnless(set(data) == set(json_data))
+
+        print 'Checking branches for "Imported layers"'
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select name from orm_branch where layer_source_id=2;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        lenght = len(data)
+        for i in range(0,lenght):
+            data[i] = data[i][0]
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_location = json_parse['layersources'][2]['name']
+        print json_location
+        json_data = json_parse['layersources'][2]['branches']
+        print json_data
+        self.failUnless(set(data) == set(json_data))
+
+
+        ##############
+        #  CASE 1077 #
+        ##############
+    def test_1077(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select name from orm_bitbakeversion;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        for i in range(0,4):
+            data[i] = data[i][0]
+        print data
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_data = []
+        for i in range(0,4):
+            json_data.append(json_parse['bitbake'][i]['name'])
+        print json_data
+        self.failUnless(set(data) == set(json_data))
+
+        ##############
+        #  CASE 1083 #
+        ##############
+    def test_1083(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_id("new-project-button").click()
+        self.driver.find_element_by_id("new-project-name").send_keys("new-test-project")
+        self.driver.find_element_by_id("create-project-button").click()
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select count(name) from orm_project where name = 'new-test-project';"
+        cursor.execute(query)
+        data = cursor.fetchone()
+        print 'data: %s' % data
+        self.failUnless(data >= 1)
+
+        ##############
+        #  CASE 1084 #
+        ##############
+    def test_1084(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_id("new-project-button").click()
+        self.driver.find_element_by_id("new-project-name").send_keys("new-default-project")
+        self.driver.find_element_by_id("create-project-button").click()
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select a.name from orm_release a, orm_project b where a.id = b.release_id and b.name = 'new-default-project' limit 1;"
+        cursor.execute(query)
+        db_data = str(cursor.fetchone()[0])
+        json_parse = json.loads(open('toasterconf.json').read())
+        json_data = str(json_parse['defaultrelease'])
+        self.failUnless(db_data == json_data)
+
+        ##############
+        #  CASE 1088 #
+        ##############
+    def test_1088(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
+        self.driver.find_element_by_link_text('new-default-project').click()
+        self.driver.find_element_by_id('project-change-form-toggle').click()
+        self.driver.find_element_by_id('project-name-change-input').clear()
+        self.driver.find_element_by_id('project-name-change-input').send_keys('new-name')
+        self.driver.find_element_by_id('project-name-change-btn').click()
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select count(name) from orm_project where name = 'new-name';"
+        cursor.execute(query)
+        data = cursor.fetchone()[0]
+        self.failUnless(data == 1)
+        #reseting project name
+        self.driver.find_element_by_id('project-change-form-toggle').click()
+        self.driver.find_element_by_id('project-name-change-input').clear()
+        self.driver.find_element_by_id('project-name-change-input').send_keys('new-default-project')
+        self.driver.find_element_by_id('project-name-change-btn').click()
+
+
+        ##############
+        #  CASE 1089 #
+        ##############
+    def test_1089(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
+        self.driver.find_element_by_link_text('new-default-project').click()
+        self.driver.find_element_by_id('change-machine-toggle').click()
+        self.driver.find_element_by_id('machine-change-input').clear()
+        self.driver.find_element_by_id('machine-change-input').send_keys('qemuarm64')
+#        self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
+        self.driver.find_element_by_id('machine-change-btn').click()
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select count(id) from orm_projectvariable where name like 'machine' and value like 'qemuarm64';"
+        cursor.execute(query)
+        data = cursor.fetchone()[0]
+        self.failUnless(data == 1)
+        #resetting machine to default value
+        self.driver.find_element_by_id('change-machine-toggle').click()
+        self.driver.find_element_by_id('machine-change-input').clear()
+        self.driver.find_element_by_id('machine-change-input').send_keys('qemux86')
+        self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
+        self.driver.find_element_by_id('machine-change-btn').click()
+
+        ##############
+        #  CASE 1090 #
+        ##############
+    def test_1090(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select username from auth_user where is_superuser = 1;"
+        cursor.execute(query)
+        data = cursor.fetchall()
+        try:
+            data = data[0][0]
+        except:
+            pass
+        print data
+        self.failUnless(data == 'toaster_admin')
+
+        ##############
+        #  CASE 1091 #
+        ##############
+    def test_1091(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        self.driver.get(self.base_url)
+        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
+        self.driver.find_element_by_link_text('new-default-project').click()
+        self.driver.find_element_by_id('release-change-toggle').click()
+        dropdown = self.driver.find_element_by_css_selector('select')
+        for option in dropdown.find_elements_by_tag_name('option'):
+            if option.text == 'Local Yocto Project':
+                option.click()
+        self.driver.find_element_by_id('change-release-btn').click()
+        #wait for the changes to register in the DB
+        time.sleep(1)
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select count(*) from orm_layer_version a, orm_projectlayer b, orm_project c where a.\"commit\"=\"HEAD\" and a.id = b.layercommit_id and b.project_id=c.id and c.name='new-default-project';"
+        cursor.execute(query)
+        data = cursor.fetchone()[0]
+        #resetting release to default
+        self.driver.find_element_by_id('release-change-toggle').click()
+        dropdown = self.driver.find_element_by_css_selector('select')
+        for option in dropdown.find_elements_by_tag_name('option'):
+            if option.text == 'Yocto Project master':
+                option.click()
+        self.driver.find_element_by_id('change-release-btn').click()
+        #wait for the changes to register in the DB
+        time.sleep(1)
+        self.failUnless(data == 3)
+
+        ##############
+        #  CASE 1092 #
+        ##############
+    def test_1092(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+        self.driver.maximize_window()
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select a.name, a.value from orm_projectvariable a, orm_project b where a.project_id = b.id and b.name = 'new-default-project';"
+        cursor.execute(query)
+        data = dict(cursor.fetchall())
+        print data
+        default_values = {u'IMAGE_INSTALL_append': u'', u'PACKAGE_CLASSES': u'package_rpm', u'MACHINE': u'qemux86', u'SDKMACHINE': u'x86_64', u'DISTRO': u'poky', u'IMAGE_FSTYPES': u'ext3 jffs2 tar.bz2'}
+        self.failUnless(data == default_values)
+
+        ##############
+        #  CASE 1093 #
+        ##############
+    def test_1093(self):
+        self.case_no = self.get_case_number()
+        self.log.info(' CASE %s log: ' % str(self.case_no))
+
+        #get initial values
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
+        cursor.execute(query)
+        data_initial = cursor.fetchall()
+        print data_initial
+
+        self.driver.maximize_window()
+        self.driver.get('localhost:8000')#self.base_url)
+        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
+        self.driver.find_element_by_link_text('new-default-project').click()
+        self.driver.find_element_by_id('release-change-toggle').click()
+        dropdown = self.driver.find_element_by_css_selector('select')
+        for option in dropdown.find_elements_by_tag_name('option'):
+            if option.text == 'Local Yocto Project':
+                option.click()
+        self.driver.find_element_by_id('change-release-btn').click()
+        #wait for the changes to register in the DB
+        time.sleep(1)
+
+        #get changed values
+        con=sqlite.connect('toaster.sqlite')
+        cursor = con.cursor()
+        query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
+        cursor.execute(query)
+        data_changed = cursor.fetchall()
+        print data_changed
+
+        #resetting release to default
+        self.driver.find_element_by_id('release-change-toggle').click()
+        dropdown = self.driver.find_element_by_css_selector('select')
+        for option in dropdown.find_elements_by_tag_name('option'):
+            if option.text == 'Yocto Project master':
+                option.click()
+        self.driver.find_element_by_id('change-release-btn').click()
+        #wait for the changes to register in the DB
+        time.sleep(1)
+        self.failUnless(data_initial != data_changed)
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/8] toaster/tests: Add __init__.py and base.py.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
  2016-02-23 17:31 ` [PATCH 1/8] toaster/tests: Reorder/move current test code Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 3/8] toaster/tests: Improve ui.py module Aníbal Limón
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

Add empty __init__.py for set folder as module.

Add base.py module that contains a base class for toaster test
cases now have an object with options and a logger.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 lib/toaster/tests/__init__.py |  0
 lib/toaster/tests/base.py     | 11 +++++++++++
 2 files changed, 11 insertions(+)
 create mode 100644 lib/toaster/tests/__init__.py
 create mode 100644 lib/toaster/tests/base.py

diff --git a/lib/toaster/tests/__init__.py b/lib/toaster/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lib/toaster/tests/base.py b/lib/toaster/tests/base.py
new file mode 100644
index 0000000..8b303ac
--- /dev/null
+++ b/lib/toaster/tests/base.py
@@ -0,0 +1,11 @@
+import unittest
+
+class ToasterOptions(object): 
+    pass
+
+class ToasterTestCase(unittest.TestCase):
+    def __init__(self, testname, opts, logger):
+        super(ToasterTestCase, self).__init__(testname)
+
+        self.opts = opts
+        self.logger = logger
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/8] toaster/tests: Improve ui.py module.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
  2016-02-23 17:31 ` [PATCH 1/8] toaster/tests: Reorder/move current test code Aníbal Limón
  2016-02-23 17:31 ` [PATCH 2/8] toaster/tests: Add __init__.py and base.py Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 4/8] toaster/tests: Adds setup test case Aníbal Limón
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

Remove shebang because this module can't be executed.

Minor fix use os.path.lexists instead of os.path.exists when
test if symlink exists.

The toaster_cases_base class now inherit ToasterTestCase from
base.py it enables to use self.opts instead of hardcod'ed sqlite
db path, browser and toaster url.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 lib/toaster/tests/ui.py | 158 ++++++++++++++++++++++--------------------------
 1 file changed, 73 insertions(+), 85 deletions(-)

diff --git a/lib/toaster/tests/ui.py b/lib/toaster/tests/ui.py
index d8f838a..cb11184 100644
--- a/lib/toaster/tests/ui.py
+++ b/lib/toaster/tests/ui.py
@@ -1,4 +1,3 @@
-#!/usr/bin/python
 # Copyright
 
 # DESCRIPTION
@@ -32,6 +31,8 @@ from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.support.ui import Select
 import sqlite3 as sqlite
 
+from toaster.tests.base import ToasterTestCase
+
 
 ###########################################
 #                                         #
@@ -287,7 +288,7 @@ def LogResults(original_class):
             local_log.results("Testcase "+str(test_case)+": PASSED")
 
         # Create symlink to the current log
-        if os.path.exists(linkfile):
+        if os.path.lexists(linkfile):
             os.remove(linkfile)
         os.symlink(logfile, linkfile)
 
@@ -303,7 +304,7 @@ def LogResults(original_class):
 ###########################################
 
 @LogResults
-class toaster_cases_base(unittest.TestCase):
+class toaster_cases_base(ToasterTestCase):
 
     @classmethod
     def setUpClass(cls):
@@ -313,18 +314,6 @@ class toaster_cases_base(unittest.TestCase):
         self.screenshot_sequence = 1
         self.verificationErrors = []
         self.accept_next_alert = True
-        self.host_os = platform.system().lower()
-        if os.getenv('TOASTER_SUITE'):
-            self.target_suite = os.getenv('TOASTER_SUITE')
-        else:
-            self.target_suite = self.host_os
-
-        self.parser = ConfigParser.SafeConfigParser()
-        self.parser.read('toaster_test.cfg')
-        self.base_url = eval(self.parser.get('toaster_test_' + self.target_suite, 'toaster_url'))
-
-        # create log dir . Currently , we put log files in log/tmp. After all
-        # test cases are done, move them to log/$datetime dir
         self.log_tmp_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + 'tmp'
         try:
             mkdir_p(self.log_tmp_dir)
@@ -360,13 +349,12 @@ class toaster_cases_base(unittest.TestCase):
 
 
     def setup_browser(self, *browser_path):
-        self.browser = eval(self.parser.get('toaster_test_' + self.target_suite, 'test_browser'))
-        print self.browser
-        if self.browser == "firefox":
+        print self.opts.test_browser
+        if self.opts.test_browser == "firefox":
             driver = webdriver.Firefox()
-        elif self.browser == "chrome":
+        elif self.opts.test_browser == "chrome":
             driver = webdriver.Chrome()
-        elif self.browser == "ie":
+        elif self.opts.test_browser == "ie":
             driver = webdriver.Ie()
         else:
             driver = None
@@ -401,12 +389,12 @@ class toaster_cases_base(unittest.TestCase):
         for item in types:
             log_dir = self.log_tmp_dir + os.sep + sub_dir
             mkdir_p(log_dir)
-            log_path = log_dir + os.sep +  self.browser + '-' +\
+            log_path = log_dir + os.sep +  self.opts.test_browser + '-' +\
                     item + '-' + add_name + '-' + str(self.screenshot_sequence) + '.png'
             if item == 'native':
-                if self.host_os == "linux":
+                if self.opts.host_os == "linux":
                     os.system("scrot " + log_path)
-                elif self.host_os=="darwin":
+                elif self.opts.host_os=="darwin":
                     os.system("screencapture -x " + log_path)
             elif item == 'selenium':
                 self.driver.get_screenshot_as_file(log_path)
@@ -419,7 +407,7 @@ class toaster_cases_base(unittest.TestCase):
         But for firefox, mostly we don't need this.
         To be discussed
         """
-        if self.browser == "chrome":
+        if self.opts.test_browser == "chrome":
             time.sleep(1)
         return
 
@@ -691,7 +679,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # open all columns
         self.driver.find_element_by_id("edit-columns-button").click()
         # adding explicitly wait for chromedriver..-_-
@@ -731,7 +719,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # Could add more test patterns here in the future. Also, could search some items other than target column in future..
         patterns = ["minimal", "sato"]
         for pattern in patterns:
@@ -757,7 +745,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # when opening a new page, "started_on" is not displayed by default
         self.driver.find_element_by_id("edit-columns-button").click()
         # currently all the delay are for chrome driver -_-
@@ -806,7 +794,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_partial_link_text("core-image").click()
         self.driver.find_element_by_link_text("Tasks").click()
         self.table_name = 'otable'
@@ -947,7 +935,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'Packages').click()
         # find "bash" in first column (Packages)
@@ -984,7 +972,7 @@ class toaster_cases(toaster_cases_base):
         test_package1="busybox"
         test_package2="lib"
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text(image_type).click()
         self.driver.find_element_by_link_text("Recipes").click()
         self.save_screenshot(screenshot_type='selenium', append_name='step3')
@@ -1123,7 +1111,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'Recipes').click()
         # step 3-5
@@ -1150,7 +1138,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'Recipes').click()
         # step 3
@@ -1190,7 +1178,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'Recipes').click()
         # step 3
@@ -1217,7 +1205,7 @@ class toaster_cases(toaster_cases_base):
         test_package3="gettext-native"
         driver = self.driver
         driver.maximize_window()
-        driver.get(self.base_url)
+        driver.get(self.opts.toaster_url)
         driver.find_element_by_link_text(image_type).click()
         driver.find_element_by_link_text("Recipes").click()
         driver.find_element_by_link_text(test_package1).click()
@@ -1398,7 +1386,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         # step 3
         self.find_element_by_link_text_in_table('nav', 'Configuration').click()
@@ -1440,7 +1428,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         # step 2-3
         self.find_element_by_link_text_in_table('nav', 'Configuration').click()
@@ -1468,7 +1456,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # Step 2
         # default sequence in "Completed on" column is inverted
         c_list = self.get_table_column_text('class', 'completed_on')
@@ -1490,7 +1478,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # Please refer to case 924 requirement
         # default sequence in "Completed on" column is inverted
         c_list = self.get_table_column_text('class', 'completed_on')
@@ -1516,7 +1504,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         # Step 2-3
         self.find_element_by_link_text_in_table('nav', 'Packages').click()
@@ -1545,7 +1533,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         # Step 2-3
         self.find_element_by_link_text_in_table('nav', 'Packages').click()
@@ -1562,7 +1550,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.driver.find_element_by_link_text("Packages").click()
         #get initial table header
@@ -1582,7 +1570,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.driver.find_element_by_link_text("Packages").click()
         #search for the "bash" package -> this should definitely be present
@@ -1600,7 +1588,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         # step 1: test Recipes page stuff
         self.driver.find_element_by_link_text("Recipes").click()
@@ -1667,7 +1655,7 @@ class toaster_cases(toaster_cases_base):
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
         for item in ["Packages", "Recipes", "Tasks"]:
-            self.driver.get(self.base_url)
+            self.driver.get(self.opts.toaster_url)
             self.driver.find_element_by_link_text("core-image-minimal").click()
             self.driver.find_element_by_link_text(items).click()
 
@@ -1702,7 +1690,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.driver.find_element_by_link_text("Configuration").click()
         # step 3-4
@@ -1765,7 +1753,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'Configuration').click()
         # step 2
@@ -1801,7 +1789,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'Configuration').click()
         self.driver.find_element_by_link_text("BitBake variables").click()
@@ -1823,7 +1811,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_link_text("core-image-minimal").click()
         self.find_element_by_link_text_in_table('nav', 'core-image-minimal').click()
         # step 3
@@ -1873,7 +1861,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # step3&4: so far we're not sure if there's "successful build" or "failed
         # build".If either of them doesn't exist, we can still go on other steps
         check_list = ['Configuration', 'Tasks', 'Recipes', 'Packages', 'Time', 'CPU usage', 'Disk I/O']
@@ -1930,7 +1918,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # currently test case itself isn't responsible for creating "1 successful and
         # 1 failed build"
         has_successful_build = 1
@@ -1957,7 +1945,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.log.info(" You should manually create all images before test starts!")
         # So far the case itself is not responsable for creating all sorts of images.
         # So assuming they are already there
@@ -1973,7 +1961,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         # step 2-3 need to run manually
         self.log.info("step 2-3: checking the help message when you hover on help icon of target,\
                        tasks, recipes, packages need to run manually")
@@ -1992,7 +1980,7 @@ class toaster_cases(toaster_cases_base):
     def test_1066(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select count(name) from orm_project a, auth_user b where a.user_id = b.id and b.username='_anonuser';"
         cursor.execute(query)
@@ -2006,7 +1994,7 @@ class toaster_cases(toaster_cases_base):
     def test_1071(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select name from orm_release;"
         cursor.execute(query)
@@ -2015,7 +2003,7 @@ class toaster_cases(toaster_cases_base):
             data[i] = data[i][0]
         data.sort()
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_data = []
         for i in range (0,4):
             json_data.append(json_parse['releases'][i]['name'])
@@ -2029,7 +2017,7 @@ class toaster_cases(toaster_cases_base):
     def test_1072(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select value from orm_toastersetting where name like 'DEFCONF%';"
         cursor.execute(query)
@@ -2037,7 +2025,7 @@ class toaster_cases(toaster_cases_base):
         for i in range(0,6):
             data[i] = data[i][0]
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_data=json_parse['config']
         json_data = json_data.values()
         print json_data
@@ -2050,7 +2038,7 @@ class toaster_cases(toaster_cases_base):
     def test_1074(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select name from orm_layersource;"
         cursor.execute(query)
@@ -2058,7 +2046,7 @@ class toaster_cases(toaster_cases_base):
         for i in range(0,3):
             data[i] = data[i][0]
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_data = []
         for i in range(0,3):
             json_data.append(json_parse['layersources'][i]['name'])
@@ -2071,14 +2059,14 @@ class toaster_cases(toaster_cases_base):
     def test_1075(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select value from orm_toastersetting where name like 'DEFAULT_RELEASE';"
         cursor.execute(query)
         data = cursor.fetchall()
         data = data[0][0]
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_data = json_parse['defaultrelease']
         print json_data
         self.failUnless(set(data) == set(json_data))
@@ -2091,7 +2079,7 @@ class toaster_cases(toaster_cases_base):
         self.log.info(' CASE %s log: ' % str(self.case_no))
 
         print 'Checking branches for "Local Yocto Project"'
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select name from orm_branch where layer_source_id=1;"
         cursor.execute(query)
@@ -2103,7 +2091,7 @@ class toaster_cases(toaster_cases_base):
         except:
             pass
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_location = json_parse['layersources'][0]['name']
         print json_location
         json_data = json_parse['layersources'][0]['branches']
@@ -2111,7 +2099,7 @@ class toaster_cases(toaster_cases_base):
         self.failUnless(set(data) == set(json_data))
 
         print 'Checking branches for "OpenEmbedded"'
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select name from orm_branch where layer_source_id=3;"
         cursor.execute(query)
@@ -2120,7 +2108,7 @@ class toaster_cases(toaster_cases_base):
         for i in range(0,lenght):
             data[i] = data[i][0]
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_location = json_parse['layersources'][1]['name']
         print json_location
         json_data = json_parse['layersources'][1]['branches']
@@ -2128,7 +2116,7 @@ class toaster_cases(toaster_cases_base):
         self.failUnless(set(data) == set(json_data))
 
         print 'Checking branches for "Imported layers"'
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select name from orm_branch where layer_source_id=2;"
         cursor.execute(query)
@@ -2137,7 +2125,7 @@ class toaster_cases(toaster_cases_base):
         for i in range(0,lenght):
             data[i] = data[i][0]
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_location = json_parse['layersources'][2]['name']
         print json_location
         json_data = json_parse['layersources'][2]['branches']
@@ -2151,7 +2139,7 @@ class toaster_cases(toaster_cases_base):
     def test_1077(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select name from orm_bitbakeversion;"
         cursor.execute(query)
@@ -2159,7 +2147,7 @@ class toaster_cases(toaster_cases_base):
         for i in range(0,4):
             data[i] = data[i][0]
         print data
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_data = []
         for i in range(0,4):
             json_data.append(json_parse['bitbake'][i]['name'])
@@ -2173,11 +2161,11 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_id("new-project-button").click()
         self.driver.find_element_by_id("new-project-name").send_keys("new-test-project")
         self.driver.find_element_by_id("create-project-button").click()
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select count(name) from orm_project where name = 'new-test-project';"
         cursor.execute(query)
@@ -2192,16 +2180,16 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_id("new-project-button").click()
         self.driver.find_element_by_id("new-project-name").send_keys("new-default-project")
         self.driver.find_element_by_id("create-project-button").click()
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select a.name from orm_release a, orm_project b where a.id = b.release_id and b.name = 'new-default-project' limit 1;"
         cursor.execute(query)
         db_data = str(cursor.fetchone()[0])
-        json_parse = json.loads(open('toasterconf.json').read())
+        json_parse = json.loads(open(self.opts.toaster_config).read())
         json_data = str(json_parse['defaultrelease'])
         self.failUnless(db_data == json_data)
 
@@ -2212,14 +2200,14 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
         self.driver.find_element_by_link_text('new-default-project').click()
         self.driver.find_element_by_id('project-change-form-toggle').click()
         self.driver.find_element_by_id('project-name-change-input').clear()
         self.driver.find_element_by_id('project-name-change-input').send_keys('new-name')
         self.driver.find_element_by_id('project-name-change-btn').click()
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select count(name) from orm_project where name = 'new-name';"
         cursor.execute(query)
@@ -2239,7 +2227,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
         self.driver.find_element_by_link_text('new-default-project').click()
         self.driver.find_element_by_id('change-machine-toggle').click()
@@ -2247,7 +2235,7 @@ class toaster_cases(toaster_cases_base):
         self.driver.find_element_by_id('machine-change-input').send_keys('qemuarm64')
 #        self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
         self.driver.find_element_by_id('machine-change-btn').click()
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select count(id) from orm_projectvariable where name like 'machine' and value like 'qemuarm64';"
         cursor.execute(query)
@@ -2266,7 +2254,7 @@ class toaster_cases(toaster_cases_base):
     def test_1090(self):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select username from auth_user where is_superuser = 1;"
         cursor.execute(query)
@@ -2285,7 +2273,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        self.driver.get(self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
         self.driver.find_element_by_link_text('new-default-project').click()
         self.driver.find_element_by_id('release-change-toggle').click()
@@ -2296,7 +2284,7 @@ class toaster_cases(toaster_cases_base):
         self.driver.find_element_by_id('change-release-btn').click()
         #wait for the changes to register in the DB
         time.sleep(1)
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select count(*) from orm_layer_version a, orm_projectlayer b, orm_project c where a.\"commit\"=\"HEAD\" and a.id = b.layercommit_id and b.project_id=c.id and c.name='new-default-project';"
         cursor.execute(query)
@@ -2319,7 +2307,7 @@ class toaster_cases(toaster_cases_base):
         self.case_no = self.get_case_number()
         self.log.info(' CASE %s log: ' % str(self.case_no))
         self.driver.maximize_window()
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select a.name, a.value from orm_projectvariable a, orm_project b where a.project_id = b.id and b.name = 'new-default-project';"
         cursor.execute(query)
@@ -2336,7 +2324,7 @@ class toaster_cases(toaster_cases_base):
         self.log.info(' CASE %s log: ' % str(self.case_no))
 
         #get initial values
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
         cursor.execute(query)
@@ -2344,7 +2332,7 @@ class toaster_cases(toaster_cases_base):
         print data_initial
 
         self.driver.maximize_window()
-        self.driver.get('localhost:8000')#self.base_url)
+        self.driver.get(self.opts.toaster_url)
         self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
         self.driver.find_element_by_link_text('new-default-project').click()
         self.driver.find_element_by_id('release-change-toggle').click()
@@ -2357,7 +2345,7 @@ class toaster_cases(toaster_cases_base):
         time.sleep(1)
 
         #get changed values
-        con=sqlite.connect('toaster.sqlite')
+        con=sqlite.connect(self.opts.toaster_db)
         cursor = con.cursor()
         query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
         cursor.execute(query)
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/8] toaster/tests: Adds setup test case.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
                   ` (2 preceding siblings ...)
  2016-02-23 17:31 ` [PATCH 3/8] toaster/tests: Improve ui.py module Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 5/8] toaster/tests: Add helpers.py module Aníbal Limón
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

The setup test case is responsible to leave toaster in certain
state for execute ui tests now it creates selenium-project and
builds core-image-{minimal, sato} images.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 lib/toaster/tests/setup.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 lib/toaster/tests/setup.py

diff --git a/lib/toaster/tests/setup.py b/lib/toaster/tests/setup.py
new file mode 100644
index 0000000..0acbb16
--- /dev/null
+++ b/lib/toaster/tests/setup.py
@@ -0,0 +1,77 @@
+import time
+
+from selenium import webdriver
+from selenium.common.exceptions import NoSuchElementException
+from selenium import selenium
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support.ui import Select
+
+from toaster.tests.base import ToasterTestCase
+
+class ToasterSetupTestCase(ToasterTestCase):
+    def setUp(self):
+        self.driver = webdriver.Firefox()
+
+    def tearDown(self):
+        self.driver.close()
+
+    def is_text_present (self, patterns):
+        for pattern in patterns:
+            if str(pattern) not in self.driver.page_source:
+                self.logger.debug(pattern)
+                return False
+        return True
+
+    def test_setupToaster(self):
+        self.driver.maximize_window()
+        self.driver.get(self.opts.toaster_url)
+        try:
+            self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
+        except:
+            self.driver.find_element_by_id("new-project-button").click()
+            self.driver.find_element_by_id("new-project-name").send_keys("selenium-project")
+            self.driver.find_element_by_id("create-project-button").click()
+
+        try:
+            self.driver.find_element_by_link_text("selenium-project").click()
+        except:
+            self.driver.find_element_by_id("new-project-button").click()
+            self.driver.find_element_by_id("new-project-name").send_keys("selenium-project")
+            self.driver.find_element_by_id("create-project-button").click()
+        time.sleep(5)
+
+        #queue up a core-image-minimal
+        self.driver.find_element_by_id("build-input").send_keys("core-image-minimal")
+        self.driver.find_element_by_id("build-button").click()
+        time.sleep(5)
+        #queue up a core-image-sato
+        self.driver.find_element_by_id("build-input").send_keys("core-image-sato")
+        self.driver.find_element_by_id("build-button").click()
+        time.sleep(5)
+
+        #go back to the main project page
+        self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
+        time.sleep(5)
+        self.driver.find_element_by_link_text("selenium-project").click()
+        time.sleep(5)
+
+        #move to all builds page
+        self.driver.find_element_by_css_selector("a[href='/toastergui/builds/']").click()
+        self.driver.refresh()
+        time.sleep(5)
+
+        #check progress bar is displayed to signal a build has started
+        try:
+            while (self.driver.find_element_by_xpath("//div[@class='progress']").is_displayed()):
+                self.logger.info('Builds are running ...')
+                self.driver.refresh()
+                time.sleep(15)
+        except:
+            pass
+
+        self.logger.info("Looking for successful build...")
+        if (self.driver.find_element_by_xpath("//div[@class='alert build-result alert-success project-name']").is_displayed()):
+            self.logger.info("Builds complete!")
+        else:
+            self.fail(msg="Builds did not complete successfully.")
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 5/8] toaster/tests: Add helpers.py module.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
                   ` (3 preceding siblings ...)
  2016-02-23 17:31 ` [PATCH 4/8] toaster/tests: Adds setup test case Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 6/8] toaster-test: Refactor and imporvements Aníbal Limón
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

The helpers.py module is a utility for clone/setup_venv/start/stop
a toaster instance.

It can be executed in a standalone way or use as utility class.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 lib/toaster/tests/helpers.py | 178 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)
 create mode 100755 lib/toaster/tests/helpers.py

diff --git a/lib/toaster/tests/helpers.py b/lib/toaster/tests/helpers.py
new file mode 100755
index 0000000..549358b
--- /dev/null
+++ b/lib/toaster/tests/helpers.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2016 Intel Corporation
+#
+
+import subprocess
+import os
+import shutil
+import signal
+import tempfile
+
+from proc.core import find_processes
+
+TOASTER_TEST_BRANCH = 'toaster_tests'
+VENV_NAME = 'venv'
+SHELL_CMD = os.environ['SHELL'] if 'SHELL' in os.environ else "/bin/bash"
+TOASTER_BUILD_DIR_NAME = 'build'
+
+def _check_output1(*popenargs, **kwargs):
+    """
+        Almost the same as subprocess.check_output but change the stdout from
+        PIPE to tempfile to avoid deadlocks when trying to read the PIPE using
+        communicate(). This scenario can be seen calling toaster_start on failure
+        scenarios.
+
+        This causes a little overhead by the tempfile.
+    """
+
+    f = tempfile.TemporaryFile(mode='rw+')
+    if 'stdout' in kwargs:
+        raise ValueError('stdout argument not allowed, it will be overridden.')
+    process = subprocess.Popen(stdout=f, *popenargs, **kwargs)
+    retcode = process.wait()
+
+    f.flush()
+    os.fsync(f.fileno())
+    f.seek(0, 0)
+    output = f.read()
+    f.close()
+
+    if retcode:
+        cmd = kwargs.get("args")
+        if cmd is None:
+            cmd = popenargs[0]
+        raise subprocess.CalledProcessError(retcode, cmd, output=output)
+    return output
+
+class ToasterHelper(object):
+    def __init__(self, directory, repo, repo_ref='master', port='8000',
+            build_dir=TOASTER_BUILD_DIR_NAME):
+        self.directory = directory
+        self.repo = repo
+        self.repo_ref = repo_ref
+        self.port = port
+        self.build_dir = build_dir
+
+    def _execute_command(self, cmd):
+        return _check_output1([SHELL_CMD, "-c", "cd %s; %s" % \
+            (self.directory, cmd)], stderr=subprocess.STDOUT)
+
+    def _execute_command_oe(self, cmd):
+        return self._execute_command("source oe-init-build-env %s; %s"\
+            % (self.build_dir, cmd))
+
+    def _execute_command_oe_venv(self, cmd):
+        return self._execute_command_oe("source %s/bin/activate; %s"\
+                % (VENV_NAME, cmd))
+
+    def clone(self, rm=False):
+        if os.path.exists(self.directory):
+            if rm:
+                shutil.rmtree(self.directory)
+            else:
+                raise IOError
+
+        subprocess.check_output([SHELL_CMD, "-c", "git clone %s %s" %\
+            (self.repo, self.directory)], stderr=subprocess.STDOUT)
+        self._execute_command("git checkout %s; git branch %s; git checkout %s;"\
+            % (self.repo_ref, TOASTER_TEST_BRANCH, TOASTER_TEST_BRANCH))
+
+    def setup(self):
+        self._execute_command_oe("virtualenv %s" % VENV_NAME)
+        self._execute_command_oe_venv("pip install -r" \
+            " ../bitbake/toaster-requirements.txt")
+        self._execute_command_oe_venv("pip install -r" \
+            " ../bitbake/toaster-tests-requirements.txt")
+
+    def start(self):
+        return self._execute_command_oe_venv("source %s/bitbake/bin/toaster"\
+            " start webport=%s" % (self.directory, self.port))
+
+    def _stop_force(self):
+        """
+            The _stop_force method iterates over the /proc and search for toaster path
+            in the process cmdline then send SIGKILL for every matched process.
+        """
+
+        pids = []
+        for p in find_processes():
+            if len(p.cmdline) > 1 and \
+                os.path.basename(p.cmdline[0]) == 'python' and \
+                p.cmdline[1].startswith(self.directory):
+                    pids.append(p.pid)
+
+        for pid in pids:
+            try:
+                os.kill(pid, signal.SIGKILL)
+            except:
+                pass
+
+        return ''
+
+    def stop(self, force=False):
+        """
+            The stop method have force mode because toaster without production 
+            setup have known issues when is on load, the server response 503
+            service unavailable.
+        """
+        if force:
+            return self._stop_force()
+        else:
+            return self._execute_command_oe_venv("source %s/bitbake/bin/toaster"\
+                " stop webport=%s" % (self.directory, self.port))
+
+    def get_sqlite_path(self):
+        return os.path.join(self.directory, self.build_dir, 'toaster.sqlite')
+
+    def get_conf_path(self):
+        output = self._execute_command_oe_venv("source %s/bitbake/bin/toaster"\
+            " start webport=%s; echo $TOASTER_CONF;" % \
+            (self.directory, self.port))
+
+        return output.split()[-1]
+
+DEFAULT_WORK_DIRECTORY = '/tmp/toaster'
+DEFAULT_POKY_URL = 'http://git.yoctoproject.org/git/poky.git'
+DEFAULT_POKY_BRANCH = 'master'
+DEFAULT_WEB_PORT = '8000'
+
+if __name__ == "__main__":
+    import argparse
+    import sys
+
+    parser = argparse.ArgumentParser(description='Toaster helper',
+        formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument("-a", "--action", choices=['clone', 'setup', 'start', 'stop'],
+        required=True,
+        help="Action to execute can be clone/setup/start/stop")
+    parser.add_argument("-d", "--work-directory", default=DEFAULT_WORK_DIRECTORY,
+        help="Directory for setup toaster, default: %s" % DEFAULT_WORK_DIRECTORY)
+    parser.add_argument("-u", "--url-repository", default=DEFAULT_POKY_URL,
+        help="GIT repository for setup toaster, default %s" % DEFAULT_POKY_URL)
+    parser.add_argument("-r", "--revision", default=DEFAULT_POKY_BRANCH,
+        help="GIT repository revision (branch, tag, hash) for setup toaster" \
+        ", default: %s" % DEFAULT_POKY_BRANCH)
+    parser.add_argument("-p", "--web-port", default=DEFAULT_WEB_PORT,
+        help="Web port for start toaster server, default: %s" % DEFAULT_WEB_PORT)
+    parser.add_argument("-b", "--build-dir", default=TOASTER_BUILD_DIR_NAME,
+        help="Toaster build directory name, default: %s" % TOASTER_BUILD_DIR_NAME)
+
+    args = parser.parse_args()
+
+    try:
+        th = ToasterHelper(args.work_directory, args.url_repository,
+            args.revision, args.web_port, args.build_dir)
+        if args.action == 'stop':
+            th.stop(force=True)
+        else:
+            getattr(th, args.action)()
+    except subprocess.CalledProcessError as e:
+        print e.output
+        raise
+    except IOError as e:
+        if args.action == 'clone':
+            print "ERROR: Directory exists: %s" % args.work_directory
+        raise 
+
+    sys.exit(0)
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 6/8] toaster-test: Refactor and imporvements.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
                   ` (4 preceding siblings ...)
  2016-02-23 17:31 ` [PATCH 5/8] toaster/tests: Add helpers.py module Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 7/8] toaster/tests: Add README, TODO and requeriments Aníbal Limón
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

Add command line options for specify config, toaster_config,
database, toaster url instance, web browser and verbose/debug.
Some options (toaster url, test cases) are read from conf first
and then command line option could override it.

Options are passed to the unit tests that now uses ToasterTestCase
base class. Also the logger is initialized and passed to the test
cases.

The logic for discover tests was improved now it looks at a fixed
dictionary test_suites and could be extended easily.

The setup test case is executed first before another test cases
like the UI one. If setup test case fails another tests aren't
executed.

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 bin/toaster-test | 278 ++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 172 insertions(+), 106 deletions(-)

diff --git a/bin/toaster-test b/bin/toaster-test
index 2b312cb..b0c26dc 100755
--- a/bin/toaster-test
+++ b/bin/toaster-test
@@ -1,155 +1,221 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 
-# Copyright
+import sys
+import os
+import argparse
+import unittest
+import logging
 
-# DESCRIPTION
-# This is script for running all selected toaster cases on
-# selected web browsers manifested in toaster_test.cfg.
+import importlib
 
-# 1. How to start toaster in yocto:
-# $ source poky/oe-init-build-env
-# $ source toaster start
-# $ bitbake core-image-minimal
+bitbake_libdir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')
+sys.path.insert(0, bitbake_libdir)
 
-# 2. How to install selenium on Ubuntu:
-# $ sudo apt-get install scrot python-pip
-# $ sudo pip install selenium
+from toaster.tests.base import ToasterOptions
+from toaster.tests.setup import ToasterSetupTestCase
 
-# 3. How to install selenium addon in firefox:
-# Download the lastest firefox addon from http://release.seleniumhq.org/selenium-ide/
-# Then install it. You can also install firebug and firepath addon
+DEFAULT_TOASTER_TESTS_CFG = os.path.join(bitbake_libdir,  'toaster', 'tests',
+    'toaster_test.cfg')
+DEFAULT_TOASTER_CFG = os.path.join(os.environ['BUILDDIR'],  '..', 'meta-yocto',
+    'conf', 'toasterconf.json')
+DEFAULT_TOASTER_DB = os.path.join(os.environ['BUILDDIR'], 'toaster.sqlite')
 
-# 4. How to start writing a new case:
-# All you need to do is to implement the function test_xxx() and  pile it on.
+test_suites = {}
+test_suites['toaster.tests.ui'] = ['toaster_cases']
 
-# 5. How to test with Chrome browser
-# Download/install chrome on host
-# Download chromedriver from https://code.google.com/p/chromedriver/downloads/list  according to your host type
-# put chromedriver in PATH, (e.g. /usr/bin/, bear in mind to chmod)
-# For windows host, you may put chromedriver.exe in the same directory as chrome.exe
+logger = None
 
-import unittest, sys, os, platform
-import ConfigParser
-import argparse
-from toaster_automation_test import toaster_cases
+def load_test(test, args, logger):
+    """
+        Returns an instance of class passing arguments
+        to the class.
+    """
 
+    module_name = '.'.join(test.split('.')[:-2])
+    class_name = test.split('.')[-2]
+    test_name = test.split('.')[-1]
 
-def get_args_parser():
-    description = "Script that runs toaster auto tests."
-    parser = argparse.ArgumentParser(description=description)
-    parser.add_argument('--run-all-tests', required=False, action="store_true", dest="run_all_tests", default=False,
-                       help='Run all tests.')
-    parser.add_argument('--run-suite', required=False, dest='run_suite', default=False,
-                       help='run suite (defined in cfg file)')
+    module = importlib.import_module(module_name)
+    clss = getattr(module, class_name)
 
-    return parser
+    return clss(test_name, args, logger)
 
+def get_tests(tests_filter=None):
+    """
+        Iterate over available tests suites and serach
+        for tests that starts with test_.
+
+        If tests_filter is specified only add tests that
+        are in the test_filter list.
+    """
 
-def get_tests():
     testslist = []
+    tests_filter_dic = None
+
+    if tests_filter:
+        tests_filter_dic = dict((str(tf), False) for tf in tests_filter)
+
+    def _filter_tests(test, tests_filter_dic):
+        if not tests_filter_dic:
+            return True
+        if test in tests_filter_dic.keys():
+            tests_filter_dic[test] = True
+            return True
+        return False
+
+    for ts in test_suites.keys():
+        m = importlib.import_module(ts)
+        for clss in test_suites[ts]:
+            for o in dir(getattr(m, clss)):
+                full_clss = '.'.join((ts, clss))
+                if o.startswith('test_') and _filter_tests(o[5:], tests_filter_dic):
+                    testslist.append('.'.join((full_clss, o)))
 
-    prefix = 'toaster_automation_test.toaster_cases'
+    return testslist
 
-    for t in dir(toaster_cases):
-        if t.startswith('test_'):
-            testslist.append('.'.join((prefix, t)))
+def get_all_tests():
+    return get_tests()
 
-    return testslist
+def get_tests_from_cfg(config, suite):
+    try:
+        tests_from_cfg = eval(config.get('toaster_test_' + suite, 'test_cases'))
+    except:
+        logger.error('Failed to get test cases from config file.' \
+            'Make sure the format is correct.')
+        raise
 
+    testslist = get_tests(tests_from_cfg)
 
-def get_tests_from_cfg(suite=None):
+    return testslist
 
-    testslist = []
-    config = ConfigParser.SafeConfigParser()
-    config.read('toaster_test.cfg')
+def buildResultClass(args):
+    """Build a Result Class to use in the testcase execution"""
 
-    if suite is not None:
-        target_suite = suite.lower()
+    class StampedResult(unittest.TextTestResult):
+        """
+        Custom TestResult that prints the time when a test starts.  As toaster-auto
+        can take a long time (ie a few hours) to run, timestamps help us understand
+        what tests are taking a long time to execute.
+        """
+        def startTest(self, test):
+            import time
+            self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
+            super(StampedResult, self).startTest(test)
 
-        # TODO: if suite is valid suite
+    return StampedResult
 
+def get_args():
+    parser = argparse.ArgumentParser(description="Script for run toaster tests.")
+
+    parser.add_argument('--run-all-tests', required=False, action="store_true",
+        dest="run_all_tests", default=False, help='Run all tests.')
+    parser.add_argument('--run-suite', required=False, dest='run_suite',
+        choices=['linux', 'windows', 'darwin'],
+        help='Run suite (defined in config file)')
+    parser.add_argument('--config', dest="config",
+        default=DEFAULT_TOASTER_TESTS_CFG,
+        help='Configuration for toaster tests, default: %s' %\
+            DEFAULT_TOASTER_TESTS_CFG)
+    parser.add_argument('--toaster-config', dest="toaster_config",
+        default=DEFAULT_TOASTER_CFG,
+        help='Configuration of toaster, default: %s' %\
+            DEFAULT_TOASTER_CFG)
+    parser.add_argument('--toaster-db', dest="toaster_db",
+        default=DEFAULT_TOASTER_DB,
+        help='Database of toaster, default: %s' %\
+            DEFAULT_TOASTER_DB)
+    parser.add_argument('--toaster-url', dest="toaster_url",
+        help='URL of toaster instance')
+    parser.add_argument('--test-browser', dest='test_browser',
+        choices=['firefox', 'chrome', 'ie'],
+        help='Web browser for use to run tests.')
+    parser.add_argument('--verbose', required=False, action="store_true",
+        dest="verbose", default=False, help='Enable verbose mode.')
+    parser.add_argument('--debug', required=False, action="store_true",
+        dest="debug", default=False, help='Enable debug mode.')
+
+    return parser.parse_args()
+
+def get_options(args):
+    opts = ToasterOptions()
+
+    opts.host_os = args.host_os
+    opts.config = args.config
+    opts.toaster_config = args.toaster_config
+    opts.toaster_db = args.toaster_db
+    opts.toaster_url = args.toaster_url
+    opts.test_browser = args.test_browser
+
+    opts.test_list = args.test_list
+
+    return opts
+
+def main(args):
+    import platform
+    import ConfigParser
+
+    if args.verbose or args.debug:
+        logging.basicConfig(stream=sys.stdout)
+        root = logging.getLogger()
+        root.setLevel(logging.DEBUG if args.debug else logging.INFO)
     else:
-        target_suite = platform.system().lower()
+        logging.basicConfig(stream=sys.stderr)
+    logger = logging.getLogger()
 
-    try:
-        tests_from_cfg = eval(config.get('toaster_test_' + target_suite, 'test_cases'))
-    except:
-        print 'Failed to get test cases from cfg file. Make sure the format is correct.'
-        return None
+    args.host_os = platform.system().lower()
+    if not args.run_suite:
+        args.run_suite = args.host_os
 
-    prefix = 'toaster_automation_test.toaster_cases.test_'
-    for t in tests_from_cfg:
-        testslist.append(prefix + str(t))
+    config = ConfigParser.SafeConfigParser()
+    config.read(args.config)
 
-    return testslist
+    if args.run_all_tests:
+        args.test_list = get_all_tests()
+    else:
+        args.test_list = get_tests_from_cfg(config, args.run_suite)
 
-def main():
+    if not args.toaster_url:
+        args.toaster_url = eval(config.get('toaster_test_' + args.run_suite,
+            'toaster_url'))
+    if not args.test_browser:
+        args.test_browser = eval(config.get('toaster_test_' + args.run_suite,
+            'test_browser'))
 
-    # In case this script is called from other directory
-    os.chdir(os.path.abspath(sys.path[0]))
+    if not args.test_list:
+        logger.error('Failed to get test cases.')
+        sys.exit(1)
 
-    parser = get_args_parser()
-    args = parser.parse_args()
+    opts = get_options(args)
 
-    if args.run_all_tests:
-        testslist = get_tests()
-    elif args.run_suite:
-        testslist = get_tests_from_cfg(args.run_suite)
-        os.environ['TOASTER_SUITE'] = args.run_suite
-    else:
-        testslist = get_tests_from_cfg()
+    runner = unittest.TextTestRunner(verbosity=2, resultclass=buildResultClass(args))
 
-    if not testslist:
-        print 'Failed to get test cases.'
-        exit(1)
+    setup_suite = unittest.TestSuite()
+    setup_suite.addTest(ToasterSetupTestCase('test_setupToaster', opts, logger))
 
     suite = unittest.TestSuite()
-    loader = unittest.TestLoader()
-    loader.sortTestMethodsUsing = None
-    runner = unittest.TextTestRunner(verbosity=2, resultclass=buildResultClass(args))
-
-    for test in testslist:
+    for test in args.test_list:
         try:
-            suite.addTests(loader.loadTestsFromName(test))
+            suite.addTest(load_test(test, opts, logger))
         except:
-            return 1
+            logger.error("Can't load test %s" % str(test))
+            raise
 
-    result = runner.run(suite)
+    result = runner.run(setup_suite)
+    if result.wasSuccessful():
+        result = runner.run(suite)
 
     if result.wasSuccessful():
         return 0
     else:
         return 1
 
-
-def buildResultClass(args):
-    """Build a Result Class to use in the testcase execution"""
-
-    class StampedResult(unittest.TextTestResult):
-        """
-        Custom TestResult that prints the time when a test starts.  As toaster-auto
-        can take a long time (ie a few hours) to run, timestamps help us understand
-        what tests are taking a long time to execute.
-        """
-        def startTest(self, test):
-            import time
-            self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
-            super(StampedResult, self).startTest(test)
-
-    return StampedResult
-
-
 if __name__ == "__main__":
+    args = get_args()
 
     try:
-        ret = main()
+        ret = main(args)
     except:
-        ret = 1
         import traceback
-        traceback.print_exc(5)
-    finally:
-        if os.getenv('TOASTER_SUITE'):
-            del os.environ['TOASTER_SUITE']
+        ret = 1
+        traceback.print_exc()
     sys.exit(ret)
-
-
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 7/8] toaster/tests: Add README, TODO and requeriments.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
                   ` (5 preceding siblings ...)
  2016-02-23 17:31 ` [PATCH 6/8] toaster-test: Refactor and imporvements Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-02-23 17:31 ` [PATCH 8/8] toaster/tests: Adds Copyright information Aníbal Limón
  2016-03-18 16:29 ` [PATCH 0/8] Toaster improve test suite Brian Avery
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 lib/toaster/tests/README       | 64 ++++++++++++++++++++++++++++++++++++++++++
 lib/toaster/tests/TODO         | 13 +++++++++
 toaster-tests-requirements.txt |  5 ++++
 3 files changed, 82 insertions(+)
 create mode 100644 lib/toaster/tests/README
 create mode 100644 lib/toaster/tests/TODO
 create mode 100644 toaster-tests-requirements.txt

diff --git a/lib/toaster/tests/README b/lib/toaster/tests/README
new file mode 100644
index 0000000..a41db2f
--- /dev/null
+++ b/lib/toaster/tests/README
@@ -0,0 +1,64 @@
+= Toaster tests =
+
+== Host requirements ==
+
+All the test components REQUIRE bash as your default shell, you
+need to review if /bin/sh points to bash because some problems are
+detected when use dash.
+
+=== Toaster ===
+
+- virtualenv for python
+- pip for python
+
+=== Toaster UI ===
+
+- firefox web browser for use selenium
+- firefox selenium addon, for install download latest from [1].
+- vncserver (optional)
+
+[1] http://release.seleniumhq.org/selenium-ide/
+
+=== Debian INSTALL ===
+
+# apt-get install iceweasel python-virtualenv python-pip
+
+Optional:
+
+# apt-get install vnc4server
+
+== Setup ==
+
+For run any of the tests scripts you need to setup virtualenv and install
+requirements with pip.
+
+$ cd /home/user/pokydir
+$ source oe-init-build-env build
+$ virtualenv venv
+$ source venv/bin/activate
+$ pip install -r toaster-requirements.txt
+$ pip install -r toaster-tests-requirements.txt
+
+== Toaster test ==
+
+For run toaster you need to start a toaster instance and then use toaster-test
+program, it can take sometime because it setup a toaster instance and run
+testsuite over it.
+
+For start/stop a toaster instance a helper exists and need to be called with
+the path to the poky directory.
+
+$ cd /home/user/pokydir
+$ source oe-init-build-env build
+$ source venv/bin/activate
+$ ../bitbake/lib/toaster/tests/helpers.py -a start -d /home/user/pokydir -b build
+$ toaster-test --run-all-tests
+$ ../bitbake/lib/toaster/tests/helpers.py -a stop -d /home/user/pokydir -b build
+
+Optional: Toaster test suite uses selenium and spawn Firefox web browser for
+run the tests, if you are in a development machine may be is a good idea to
+start vncserver and set DISPLAY before run to don't interfer with your tasks,
+example:
+
+$ vncserver :10
+$ export DISPLAY=:10
diff --git a/lib/toaster/tests/TODO b/lib/toaster/tests/TODO
new file mode 100644
index 0000000..9c7e47c
--- /dev/null
+++ b/lib/toaster/tests/TODO
@@ -0,0 +1,13 @@
+setup.py:
+
+    - Improve logic of timeout now have a fixed time,
+      will be better to ask toaster instance about the
+      state of image building.
+
+ui.py:
+    - Class toaster_cases_base review and move common code
+      to ToasterTestCase.
+    - Class toaster_cases split into ToasterUITestCases (< 1000)
+      and ToasterBackendTestCases (> 1000).
+    - Class toaster_cases fix current tests to match Toaster in
+      master (css id's update)
diff --git a/toaster-tests-requirements.txt b/toaster-tests-requirements.txt
new file mode 100644
index 0000000..90eeda4
--- /dev/null
+++ b/toaster-tests-requirements.txt
@@ -0,0 +1,5 @@
+# helpers, for stop force
+proc
+
+# ui, general
+selenium
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 8/8] toaster/tests: Adds Copyright information.
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
                   ` (6 preceding siblings ...)
  2016-02-23 17:31 ` [PATCH 7/8] toaster/tests: Add README, TODO and requeriments Aníbal Limón
@ 2016-02-23 17:31 ` Aníbal Limón
  2016-03-18 16:29 ` [PATCH 0/8] Toaster improve test suite Brian Avery
  8 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-02-23 17:31 UTC (permalink / raw)
  To: toaster; +Cc: benjamin.esquivel, william.c.randle

Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
---
 bin/toaster-test             | 17 +++++++++++++++++
 lib/toaster/tests/base.py    | 17 +++++++++++++++++
 lib/toaster/tests/helpers.py | 14 ++++++++++++++
 lib/toaster/tests/setup.py   | 17 +++++++++++++++++
 lib/toaster/tests/ui.py      | 17 ++++++++++++++++-
 5 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/bin/toaster-test b/bin/toaster-test
index b0c26dc..d256629 100755
--- a/bin/toaster-test
+++ b/bin/toaster-test
@@ -1,5 +1,22 @@
 #!/usr/bin/env python
 
+# Toaster test runner
+#
+# Copyright (C) 2015-2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
 import sys
 import os
 import argparse
diff --git a/lib/toaster/tests/base.py b/lib/toaster/tests/base.py
index 8b303ac..3c13612 100644
--- a/lib/toaster/tests/base.py
+++ b/lib/toaster/tests/base.py
@@ -1,3 +1,20 @@
+# Toaster tests base class
+#
+# Copyright (C) 2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
 import unittest
 
 class ToasterOptions(object): 
diff --git a/lib/toaster/tests/helpers.py b/lib/toaster/tests/helpers.py
index 549358b..ed152b2 100755
--- a/lib/toaster/tests/helpers.py
+++ b/lib/toaster/tests/helpers.py
@@ -1,7 +1,21 @@
 #!/usr/bin/env python
 
+# Toaster helper for clone/setup/start/stop
+#
 # Copyright (C) 2016 Intel Corporation
 #
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 import subprocess
 import os
diff --git a/lib/toaster/tests/setup.py b/lib/toaster/tests/setup.py
index 0acbb16..725e858 100644
--- a/lib/toaster/tests/setup.py
+++ b/lib/toaster/tests/setup.py
@@ -1,3 +1,20 @@
+# Toaster setup test case
+#
+# Copyright (C) 2015-2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
 import time
 
 from selenium import webdriver
diff --git a/lib/toaster/tests/ui.py b/lib/toaster/tests/ui.py
index cb11184..74dc9ce 100644
--- a/lib/toaster/tests/ui.py
+++ b/lib/toaster/tests/ui.py
@@ -1,4 +1,19 @@
-# Copyright
+# Toaster UI/Backend test cases
+#
+# Copyright (C) 2015-2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 # DESCRIPTION
 # This is toaster automation base class and test cases file
-- 
2.1.4



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 0/8] Toaster improve test suite
  2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
                   ` (7 preceding siblings ...)
  2016-02-23 17:31 ` [PATCH 8/8] toaster/tests: Adds Copyright information Aníbal Limón
@ 2016-03-18 16:29 ` Brian Avery
  2016-03-18 16:55   ` Aníbal Limón
  8 siblings, 1 reply; 11+ messages in thread
From: Brian Avery @ 2016-03-18 16:29 UTC (permalink / raw)
  To: Aníbal Limón; +Cc: william.c.randle, benjamin.esquivel, toaster

Hi,

I finally ran through the new version of the patch but I can't get it to run:
A couple of issues
1) it needs rebasing on toaster-next, the rebase went cleanly though :)
2) it can't find toasterconf.json (this can be worked around by
setting TOASTER_CONF env variable for a quick test)
3) relies on being in /home/user/pokydir - this means it's unlikely
anyone but AB would ever run it, I'd prefer that at least the top POKY
dir is not a hard path
4) migrations are failing:
django.db.utils.OperationalError: cannot rollback - no transaction is active
Failed migrations, aborting system start
Failed start.

-b
an intel employee

On Tue, Feb 23, 2016 at 9:31 AM, Aníbal Limón
<anibal.limon@linux.intel.com> wrote:
> This set of changes refactor/improve the current test suite now it can be
> parameterized from command line.
>
> The main goal for this set of patches are for integrate the toaster
> test suite into AB. The AB part is already on master. [1] If this patchset
> is merged the way for run Toaster test suite will change for use it read
> README in this patchset.
>
> Futher work are related to improve/fix the current test suite also i added
> a TODO list.
>
> Also the changes can be found at,
>
> http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=alimon/toaster_tests_squash
>
> [1] http://git.yoctoproject.org/cgit/cgit.cgi/yocto-autobuilder/commit/?id=4ad04739d7414046775be84eeb2553d12acf94ef
>
> Aníbal Limón (8):
>   toaster/tests: Reorder/move current test code.
>   toaster/tests: Add __init__.py and base.py.
>   toaster/tests: Improve ui.py module.
>   toaster/tests: Adds setup test case.
>   toaster/tests: Add helpers.py module.
>   toaster-test: Refactor and imporvements.
>   toaster/tests: Add README, TODO and requeriments.
>   toaster/tests: Adds Copyright information.
>
>  bin/toaster-test                                   |  238 ++
>  .../contrib/tts/toasteruitest/run_toastertests.py  |  155 --
>  .../tts/toasteruitest/toaster_automation_test.py   | 2376 -------------------
>  .../contrib/tts/toasteruitest/toaster_test.cfg     |   25 -
>  lib/toaster/tests/README                           |   64 +
>  lib/toaster/tests/TODO                             |   13 +
>  lib/toaster/tests/__init__.py                      |    0
>  lib/toaster/tests/base.py                          |   28 +
>  lib/toaster/tests/helpers.py                       |  192 ++
>  lib/toaster/tests/setup.py                         |   94 +
>  lib/toaster/tests/toaster_test.cfg                 |   25 +
>  lib/toaster/tests/ui.py                            | 2379 ++++++++++++++++++++
>  toaster-tests-requirements.txt                     |    5 +
>  13 files changed, 3038 insertions(+), 2556 deletions(-)
>  create mode 100755 bin/toaster-test
>  delete mode 100755 lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
>  delete mode 100755 lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
>  delete mode 100644 lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
>  create mode 100644 lib/toaster/tests/README
>  create mode 100644 lib/toaster/tests/TODO
>  create mode 100644 lib/toaster/tests/__init__.py
>  create mode 100644 lib/toaster/tests/base.py
>  create mode 100755 lib/toaster/tests/helpers.py
>  create mode 100644 lib/toaster/tests/setup.py
>  create mode 100644 lib/toaster/tests/toaster_test.cfg
>  create mode 100644 lib/toaster/tests/ui.py
>  create mode 100644 toaster-tests-requirements.txt
>
> --
> 2.1.4
>


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 0/8] Toaster improve test suite
  2016-03-18 16:29 ` [PATCH 0/8] Toaster improve test suite Brian Avery
@ 2016-03-18 16:55   ` Aníbal Limón
  0 siblings, 0 replies; 11+ messages in thread
From: Aníbal Limón @ 2016-03-18 16:55 UTC (permalink / raw)
  To: Brian Avery; +Cc: william.c.randle, benjamin.esquivel, toaster

[-- Attachment #1: Type: text/plain, Size: 4196 bytes --]

Hi,

Comments below,

Kind regards,
	alimon

On 03/18/2016 10:29 AM, Brian Avery wrote:
> Hi,
> 
> I finally ran through the new version of the patch but I can't get it to run:
> A couple of issues
> 1) it needs rebasing on toaster-next, the rebase went cleanly though :)

I rebased it.

> 2) it can't find toasterconf.json (this can be worked around by
> setting TOASTER_CONF env variable for a quick test)

How you are starting toaster?, I'm using the helper that i create,
after source oe-env and create virtualenv. [1]

> 3) relies on being in /home/user/pokydir - this means it's unlikely
> anyone but AB would ever run it, I'd prefer that at least the top POKY
> dir is not a hard path

If you mean in the instructions [1] is only for example it works for any
directory.


> 4) migrations are failing:
> django.db.utils.OperationalError: cannot rollback - no transaction is active
> Failed migrations, aborting system start
> Failed start.

I'm not hitting this issue, may be is related to another thing.

[1]
http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/tree/bitbake/lib/toaster/tests/README?h=alimon/toaster_tests_squash#n30

> 
> -b
> an intel employee
> 
> On Tue, Feb 23, 2016 at 9:31 AM, Aníbal Limón
> <anibal.limon@linux.intel.com> wrote:
>> This set of changes refactor/improve the current test suite now it can be
>> parameterized from command line.
>>
>> The main goal for this set of patches are for integrate the toaster
>> test suite into AB. The AB part is already on master. [1] If this patchset
>> is merged the way for run Toaster test suite will change for use it read
>> README in this patchset.
>>
>> Futher work are related to improve/fix the current test suite also i added
>> a TODO list.
>>
>> Also the changes can be found at,
>>
>> http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=alimon/toaster_tests_squash
>>
>> [1] http://git.yoctoproject.org/cgit/cgit.cgi/yocto-autobuilder/commit/?id=4ad04739d7414046775be84eeb2553d12acf94ef
>>
>> Aníbal Limón (8):
>>   toaster/tests: Reorder/move current test code.
>>   toaster/tests: Add __init__.py and base.py.
>>   toaster/tests: Improve ui.py module.
>>   toaster/tests: Adds setup test case.
>>   toaster/tests: Add helpers.py module.
>>   toaster-test: Refactor and imporvements.
>>   toaster/tests: Add README, TODO and requeriments.
>>   toaster/tests: Adds Copyright information.
>>
>>  bin/toaster-test                                   |  238 ++
>>  .../contrib/tts/toasteruitest/run_toastertests.py  |  155 --
>>  .../tts/toasteruitest/toaster_automation_test.py   | 2376 -------------------
>>  .../contrib/tts/toasteruitest/toaster_test.cfg     |   25 -
>>  lib/toaster/tests/README                           |   64 +
>>  lib/toaster/tests/TODO                             |   13 +
>>  lib/toaster/tests/__init__.py                      |    0
>>  lib/toaster/tests/base.py                          |   28 +
>>  lib/toaster/tests/helpers.py                       |  192 ++
>>  lib/toaster/tests/setup.py                         |   94 +
>>  lib/toaster/tests/toaster_test.cfg                 |   25 +
>>  lib/toaster/tests/ui.py                            | 2379 ++++++++++++++++++++
>>  toaster-tests-requirements.txt                     |    5 +
>>  13 files changed, 3038 insertions(+), 2556 deletions(-)
>>  create mode 100755 bin/toaster-test
>>  delete mode 100755 lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
>>  delete mode 100755 lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
>>  delete mode 100644 lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
>>  create mode 100644 lib/toaster/tests/README
>>  create mode 100644 lib/toaster/tests/TODO
>>  create mode 100644 lib/toaster/tests/__init__.py
>>  create mode 100644 lib/toaster/tests/base.py
>>  create mode 100755 lib/toaster/tests/helpers.py
>>  create mode 100644 lib/toaster/tests/setup.py
>>  create mode 100644 lib/toaster/tests/toaster_test.cfg
>>  create mode 100644 lib/toaster/tests/ui.py
>>  create mode 100644 toaster-tests-requirements.txt
>>
>> --
>> 2.1.4
>>


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2016-03-18 16:54 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-02-23 17:31 [PATCH 0/8] Toaster improve test suite Aníbal Limón
2016-02-23 17:31 ` [PATCH 1/8] toaster/tests: Reorder/move current test code Aníbal Limón
2016-02-23 17:31 ` [PATCH 2/8] toaster/tests: Add __init__.py and base.py Aníbal Limón
2016-02-23 17:31 ` [PATCH 3/8] toaster/tests: Improve ui.py module Aníbal Limón
2016-02-23 17:31 ` [PATCH 4/8] toaster/tests: Adds setup test case Aníbal Limón
2016-02-23 17:31 ` [PATCH 5/8] toaster/tests: Add helpers.py module Aníbal Limón
2016-02-23 17:31 ` [PATCH 6/8] toaster-test: Refactor and imporvements Aníbal Limón
2016-02-23 17:31 ` [PATCH 7/8] toaster/tests: Add README, TODO and requeriments Aníbal Limón
2016-02-23 17:31 ` [PATCH 8/8] toaster/tests: Adds Copyright information Aníbal Limón
2016-03-18 16:29 ` [PATCH 0/8] Toaster improve test suite Brian Avery
2016-03-18 16:55   ` Aníbal Limón

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.