Repository: janeczku/calibre-web (language: python)

Issue:
Feature request: resize cover images
The cover images are height-capped at 225px. So instead of loading full-size covers (which can be quite large) calibre-web can load (pre-generated) small covers.

Webp format is also a possibility here.


Discussion / hints:
@OzzieIsaacs I just wanted to suggest that too. 

If there are 60 books loaded each page then it takes quite some time for all images to load fast because of their size.

I would really love to see this feature. That would improve the loadtime by alot.

(On my kindle for example it takes "ages" to load all the images.)
One idea: Reduce the no of books on each page (admin-> view config-> on. Of books per page 
Sure changed it to 12 just now. Its faster but still quite slow.
Is there any reason against having this suggested feature at some point in the future ?
No, it‘s just the ideas are rising with the no. of users. Another idea I currently also denied was lazy loading the pictures, so they are loaded if the browser is displaying the cover the first time. The pull request is still open, is it worth a try?
Lazy loading is sadly very buggy on most ebook readers. Thats why in my opinion it is not the best solution.

But maybe there is a solution to still use the pull request with slightly changing it. Couldn't we just check what device is accessing calibre web via the user agent and only have lazyloading automatically enabled on desktops/phones/tablets but not on ebook readers ?

Just an idea. Let me know what you think about it.
Anything new on this? My library is quite large (covers are always >225px) and fetching all covers takes ages on slower connections. Is there a reason why generating 225px thumbnails of the covers on their (global) first load isn't considered up until now? Lazyloading would surely help on smaller screens but won't do anything on normal screen sizes.
> it‘s just the ideas are rising with the no. of users...
I'm currently trying to implement all the filter stuff from several issues, and merging the other pull requests, refactoring the biggest file to be ready for the future. Please be patient, there are also issues which are way older which I haven't implemented yet. 
@OzzieIsaacs I am working on a PR to address this.

**PR will include:**
* Configurable cache directory
* Image thumbnail creation when the image is loaded the first time
* Clear cache button per book and global (admin settings only)
* Cache cleanup when a book is deleted or a new image is uploaded

**Other considerations:**
* Image thumbnails will be generated at 2x the displayed resolution for high DPS screens (450px height)
* I will consider adding thumbnail quality options, but in the mean time I've done some testing with resample type and quality percentages to cover most use-cases.
* Once the PR is merged, docker images will need to be updated to add an addition volume for the cache directory
* If time permits, I will look into improving image loading timing. Currently the page wont load on mobile until all images are loaded (I believe).

**Feedback requested:**
* Should a local cache be used for book covers served by google drive?
* Should thumbnails be generated via a task instead of on page load?
* Should the cache mechanism be more robust? As in, should cache busting be triggered via a timestamp, in some sort of lockfile, instead of deleting the thumbnail via the filesystem? (my initial take: not really necessary)

I will be certain to capture page load metrics and include them in the PR.
Lazy loading the images would help so it does not load all images 
> Lazy loading the images would help so it does not load all images

Good callout, perhaps lazy loading images can resolve:

> If time permits, I will look into improving image loading timing. Currently the page wont load on mobile until all images are loaded (I believe).

That may also be a good candidate for a follow-up PR.
@mmonkey: That sound great, was planned to be one of my next projects, but I'm very happy if you implement it.
I have the following comments: 
1) Please create the PR for the development branch, I got rid of pillow there and everything only relates on imagemagick. The only thing broken there currently should be sorting shelfs (I'm currently working there)
2) Please try to avoid many settings, there are so much settings, you can look into the issues, several users asking for functions and options, because they don't find is, as there is so much and not everything is available with everything. So please no configuration option for the cache (if somebody thinks he needs it elsewhere he shall use a symlink). I'd suggest a sub directory of the cps folder (maybe static/covers folder is a good idea, we could bypass the wsgi server ... url_for("static", ...), haven't thought it trough
3) Clear cache button per book and global (admin settings only) -> global is a good idea, I don't know if it's really needed on a per book base. Please also consider to automatically delete the cache on a database directory change
4) Regarding quality options, please think on comment 2), on the other hand there is an issue about cover sizes on kobo reader which would require low quality cover pictures to make the Kobo reader to render books faster after download (see #1347)
5) Image loading lazy loading, there was a PR regarding this I closed it long time ago, as I didn't see benefit from it (#282)

Your questions:
1) Should a local cache be used for book covers served by google drive? Definitely locally, using google drive is a pain, you can go and drink a coffee while the first book covers are loading
2) Should thumbnails be generated via a task instead of on page load? I think yes, adding them to the queue in the background task is a good idea, otherwise (especially on slow computers or with google drive enabled you will wait very long for the initial generation) I was thinking on a table holding the cover cache covers and you could add books there on startup. Another thing to consider would be changing/adding books via calibre and automatically generating covers for new/changed books (I'm thinking on checking the timestamp of the books regular in a background task)
3) Should the cache mechanism be more robust? As in, should cache busting be triggered via a timestamp, in some sort of lockfile, instead of deleting the thumbnail via the file system? (my initial take: not really necessary) Not really sure about the question, as your thinking of implementing it different from my view. I would not delete the cache at any time without asked for it (the admin button, or if Calibre-Web realizes the file is gone)
Ah, and one final remark, there is a testing repro: https://github.com/OzzieIsaacs/calibre-web-test where we need to add some tests when we are finished
> I'd suggest a sub directory of the cps folder (maybe static/covers folder is a good idea, we could bypass the wsgi server ... url_for("static", ...), haven't thought it trough

The cached image directory wouldn't really make sense under "static" perhaps we just add cps/cache. The cache directory should also be added to gitignore.

> I was thinking on a table holding the cover cache covers and you could add books there on startup.

I hadn't considered adding a table for this. However, if generating thumbnails is a task, that may work best. What do you think the best approach would be to not create a task for every book on every startup? We don't need to re-generate thumbnails for ones that already exist. Like, would there be a query that gets all the book ids which then is used to query the new table for missing ids?
> The cached image directory wouldn't really make sense under "static" perhaps we just add cps/cache. The cache directory should also be added to gitignore.

okay
> I hadn't considered adding a table for this. However, if generating thumbnails is a task, that may work best. What do you think the best approach would be to not create a task for every book on every startup? We don't need to re-generate thumbnails for ones that already exist. Like, would there be a query that gets all the book ids which then is used to query the new table for missing ids?

Yes that was how I was thinking of it. I think this may should be done once per x minutes to check if somebody has changed a cover in the background. And don't worry, this won't be the first database overarching (?) query, it's a little one liner
> Yes that was how I was thinking of it. I think this may should be done once per x minutes to check if somebody has changed a cover in the background.

@OzzieIsaacs I don't see a mechanism for scheduling tasks, am I missing something? If not, I was thinking of adding [APScheduler](https://apscheduler.readthedocs.io/en/latest/index.html).

>  I was thinking on a table holding the cover cache covers and you could add books there on startup.

What is the convention for registering things at startup? Like where in the code could the scheduling service be initialized / scheduled task be started?

Otherwise things are looking pretty good so far, and I am getting a feel for the project structure in general. My index page size went from `26Mb+` to just under `6Mb`. I am looking forward to test the speed improvements for google drive mounted libraries!

Fix patch:
diff --git a/.gitignore b/.gitignore
index 614e993693..14da8a0366 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ vendor/
 # calibre-web
 *.db
 *.log
+cps/cache
 
 .idea/
 *.bak
diff --git a/cps.py b/cps.py
index 277da288fc..aee7a7f5f0 100755
--- a/cps.py
+++ b/cps.py
@@ -44,6 +44,7 @@
 from cps.remotelogin import remotelogin
 from cps.search_metadata import meta
 from cps.error_handler import init_errorhandler
+from cps.schedule import register_scheduled_tasks, register_startup_tasks
 
 try:
     from cps.kobo import kobo, get_kobo_activated
@@ -79,6 +80,11 @@ def main():
         app.register_blueprint(kobo_auth)
     if oauth_available:
         app.register_blueprint(oauth)
+
+    # Register scheduled tasks
+    register_scheduled_tasks()
+    register_startup_tasks()
+
     success = web_server.start()
     sys.exit(0 if success else 1)
 
diff --git a/cps/__init__.py b/cps/__init__.py
index 34ccf4383d..10f580a17e 100644
--- a/cps/__init__.py
+++ b/cps/__init__.py
@@ -159,6 +159,7 @@ def create_app():
 
     return app
 
+
 @babel.localeselector
 def get_locale():
     # if a user is logged in, use the locale from the user settings
diff --git a/cps/admin.py b/cps/admin.py
index c1931ea47e..75fbeeb02d 100644
--- a/cps/admin.py
+++ b/cps/admin.py
@@ -40,11 +40,13 @@
 from sqlalchemy.sql.expression import func, or_, text
 
 from . import constants, logger, helper, services
-from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils, kobo_sync_status
+from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils, \
+    kobo_sync_status, schedule
 from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
     valid_email, check_username
 from .gdriveutils import is_gdrive_ready, gdrive_support
 from .render_template import render_title_template, get_sidebar_config
+from .services.worker import WorkerThread
 from . import debug_info, _BABEL_TRANSLATIONS
 
 try:
@@ -187,6 +189,7 @@ def admin():
                                  feature_support=feature_support, kobo_support=kobo_support,
                                  title=_(u"Admin page"), page="admin")
 
+
 @admi.route("/admin/dbconfig", methods=["GET", "POST"])
 @login_required
 @admin_required
@@ -227,6 +230,7 @@ def ajax_db_config():
 def calibreweb_alive():
     return "", 200
 
+
 @admi.route("/admin/viewconfig")
 @login_required
 @admin_required
@@ -243,6 +247,7 @@ def view_configuration():
                                  translations=translations,
                                  title=_(u"UI Configuration"), page="uiconfig")
 
+
 @admi.route("/admin/usertable")
 @login_required
 @admin_required
@@ -325,6 +330,7 @@ def list_users():
     response.headers["Content-Type"] = "application/json; charset=utf-8"
     return response
 
+
 @admi.route("/ajax/deleteuser", methods=['POST'])
 @login_required
 @admin_required
@@ -358,6 +364,7 @@ def delete_user():
     success.extend(errors)
     return Response(json.dumps(success), mimetype='application/json')
 
+
 @admi.route("/ajax/getlocale")
 @login_required
 @admin_required
@@ -503,6 +510,7 @@ def update_table_settings():
         return "Invalid request", 400
     return ""
 
+
 def check_valid_read_column(column):
     if column != "0":
         if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
@@ -510,6 +518,7 @@ def check_valid_read_column(column):
             return False
     return True
 
+
 def check_valid_restricted_column(column):
     if column != "0":
         if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
@@ -548,7 +557,6 @@ def update_view_configuration():
     _config_string(to_save, "config_default_language")
     _config_string(to_save, "config_default_locale")
 
-
     config.config_default_role = constants.selected_roles(to_save)
     config.config_default_role &= ~constants.ROLE_ANONYMOUS
 
@@ -1224,6 +1232,7 @@ def _db_configuration_update_helper():
     config.save()
     return _db_configuration_result(None, gdrive_error)
 
+
 def _configuration_update_helper():
     reboot_required = False
     to_save = request.form.to_dict()
@@ -1313,6 +1322,7 @@ def _configuration_update_helper():
 
     return _configuration_result(None, reboot_required)
 
+
 def _configuration_result(error_flash=None, reboot=False):
     resp = {}
     if error_flash:
@@ -1404,6 +1414,7 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
         log.error("Settings DB is not Writeable")
         flash(_("Settings DB is not Writeable"), category="error")
 
+
 def _delete_user(content):
     if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
                                         ub.User.id != content.id).count():
@@ -1606,6 +1617,45 @@ def update_mailsettings():
     return edit_mailsettings()
 
 
+@admi.route("/admin/scheduledtasks")
+@login_required
+@admin_required
+def edit_scheduledtasks():
+    content = config.get_scheduled_task_settings()
+    return render_title_template("schedule_edit.html", config=content, title=_(u"Edit Scheduled Tasks Settings"))
+
+
+@admi.route("/admin/scheduledtasks", methods=["POST"])
+@login_required
+@admin_required
+def update_scheduledtasks():
+    to_save = request.form.to_dict()
+    _config_int(to_save, "schedule_start_time")
+    _config_int(to_save, "schedule_end_time")
+    _config_checkbox(to_save, "schedule_generate_book_covers")
+    _config_checkbox(to_save, "schedule_generate_series_covers")
+
+    try:
+        config.save()
+        flash(_(u"Scheduled tasks settings updated"), category="success")
+
+        # Cancel any running tasks
+        schedule.end_scheduled_tasks()
+
+        # Re-register tasks with new settings
+        schedule.register_scheduled_tasks()
+    except IntegrityError as ex:
+        ub.session.rollback()
+        log.error("An unknown error occurred while saving scheduled tasks settings")
+        flash(_(u"An unknown error occurred. Please try again later."), category="error")
+    except OperationalError:
+        ub.session.rollback()
+        log.error("Settings DB is not Writeable")
+        flash(_("Settings DB is not Writeable"), category="error")
+
+    return edit_scheduledtasks()
+
+
 @admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
 @login_required
 @admin_required
@@ -1882,3 +1932,13 @@ def extract_dynamic_field_from_filter(user, filtr):
 def extract_user_identifier(user, filtr):
     dynamic_field = extract_dynamic_field_from_filter(user, filtr)
     return extract_user_data_from_field(user, dynamic_field)
+
+
+@admi.route("/ajax/canceltask", methods=['POST'])
+@login_required
+@admin_required
+def cancel_task():
+    task_id = request.get_json().get('task_id', None)
+    worker = WorkerThread.get_instance()
+    worker.end_task(task_id)
+    return ""
diff --git a/cps/config_sql.py b/cps/config_sql.py
index ebc4ca240f..bb6f0a58ca 100644
--- a/cps/config_sql.py
+++ b/cps/config_sql.py
@@ -133,13 +133,18 @@ class _Settings(_Base):
     config_calibre = Column(String)
     config_rarfile_location = Column(String, default=None)
     config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD))
-    config_unicode_filename =Column(Boolean, default=False)
+    config_unicode_filename = Column(Boolean, default=False)
 
     config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
 
     config_reverse_proxy_login_header_name = Column(String)
     config_allow_reverse_proxy_header_login = Column(Boolean, default=False)
 
+    schedule_start_time = Column(Integer, default=4)
+    schedule_end_time = Column(Integer, default=6)
+    schedule_generate_book_covers = Column(Boolean, default=False)
+    schedule_generate_series_covers = Column(Boolean, default=False)
+
     def __repr__(self):
         return self.__class__.__name__
 
@@ -170,7 +175,6 @@ def __init__(self, session):
         if change:
             self.save()
 
-
     def _read_from_storage(self):
         if self._settings is None:
             log.debug("_ConfigSQL._read_from_storage")
@@ -254,6 +258,8 @@ def get_mail_server_configured(self):
         return bool((self.mail_server != constants.DEFAULT_MAIL_SERVER and self.mail_server_type == 0)
                     or (self.mail_gmail_token != {} and self.mail_server_type == 1))
 
+    def get_scheduled_task_settings(self):
+        return {k:v for k, v in self.__dict__.items() if k.startswith('schedule_')}
 
     def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None):
         """Possibly updates a field of this object.
@@ -289,7 +295,6 @@ def toDict(self):
                 storage[k] = v
         return storage
 
-
     def load(self):
         '''Load all configuration values from the underlying storage.'''
         s = self._read_from_storage()  # type: _Settings
@@ -403,6 +408,7 @@ def autodetect_calibre_binary():
             return element
     return ""
 
+
 def autodetect_unrar_binary():
     if sys.platform == "win32":
         calibre_path = ["C:\\program files\\WinRar\\unRAR.exe",
@@ -414,6 +420,7 @@ def autodetect_unrar_binary():
             return element
     return ""
 
+
 def autodetect_kepubify_binary():
     if sys.platform == "win32":
         calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe",
@@ -425,6 +432,7 @@ def autodetect_kepubify_binary():
             return element
     return ""
 
+
 def _migrate_database(session):
     # make sure the table is created, if it does not exist
     _Base.metadata.create_all(session.bind)
@@ -448,6 +456,7 @@ def load_configuration(session):
     #    session.commit()
     return conf
 
+
 def get_flask_session_key(session):
     flask_settings = session.query(_Flask_Settings).one_or_none()
     if flask_settings == None:
diff --git a/cps/constants.py b/cps/constants.py
index 0a964fb732..3cb8770625 100644
--- a/cps/constants.py
+++ b/cps/constants.py
@@ -23,10 +23,13 @@
 
 sqlalchemy_version2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0])
 
+# APP_MODE - production, development, or test
+APP_MODE             = os.environ.get('APP_MODE', 'production')
+
 # if installed via pip this variable is set to true (empty file with name .HOMEDIR present)
 HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR'))
 
-#In executables updater is not available, so variable is set to False there
+# In executables updater is not available, so variable is set to False there
 UPDATER_AVAILABLE = True
 
 # Base dir is parent of current file, necessary if called from different folder
@@ -35,6 +38,10 @@
 TEMPLATES_DIR       = os.path.join(BASE_DIR, 'cps', 'templates')
 TRANSLATIONS_DIR    = os.path.join(BASE_DIR, 'cps', 'translations')
 
+# Cache dir - use CACHE_DIR environment variable, otherwise use the default directory: cps/cache
+DEFAULT_CACHE_DIR   = os.path.join(BASE_DIR, 'cps', 'cache')
+CACHE_DIR           = os.environ.get('CACHE_DIR', DEFAULT_CACHE_DIR)
+
 if HOME_CONFIG:
     home_dir = os.path.join(os.path.expanduser("~"),".calibre-web")
     if not os.path.exists(home_dir):
@@ -159,6 +166,19 @@ def selected_roles(dictionary):
 # NIGHTLY_VERSION[0] = 'bb7d2c6273ae4560e83950d36d64533343623a57'
 # NIGHTLY_VERSION[1] = '2018-09-09T10:13:08+02:00'
 
+# CACHE
+CACHE_TYPE_THUMBNAILS    = 'thumbnails'
+
+# Thumbnail Types
+THUMBNAIL_TYPE_COVER     = 1
+THUMBNAIL_TYPE_SERIES    = 2
+THUMBNAIL_TYPE_AUTHOR    = 3
+
+# Thumbnails Sizes
+COVER_THUMBNAIL_ORIGINAL = 0
+COVER_THUMBNAIL_SMALL    = 1
+COVER_THUMBNAIL_MEDIUM   = 2
+COVER_THUMBNAIL_LARGE    = 3
 
 # clean-up the module namespace
 del sys, os, namedtuple
diff --git a/cps/db.py b/cps/db.py
index aa697ec605..db97f06ae1 100644
--- a/cps/db.py
+++ b/cps/db.py
@@ -444,11 +444,11 @@ def __init__(self, expire_on_commit=True):
         """
         self.session = None
         if self._init:
-            self.initSession(expire_on_commit)
+            self.init_session(expire_on_commit)
 
         self.instances.add(self)
 
-    def initSession(self, expire_on_commit=True):
+    def init_session(self, expire_on_commit=True):
         self.session = self.session_factory()
         self.session.expire_on_commit = expire_on_commit
         self.update_title_sort(self.config)
@@ -591,7 +591,7 @@ def setup_db(cls, config_calibre_dir, app_db_path):
                                                           autoflush=True,
                                                           bind=cls.engine))
         for inst in cls.instances:
-            inst.initSession()
+            inst.init_session()
 
         cls._init = True
         return True
@@ -708,7 +708,8 @@ def fill_indexpage_with_archived_books(self, page, database, pagesize, db_filter
             randm = self.session.query(Books) \
                 .filter(self.common_filters(allow_show_archived)) \
                 .order_by(func.random()) \
-                .limit(self.config.config_random_books).all()
+                .limit(self.config.config_random_books) \
+                .all()
         else:
             randm = false()
         if join_archive_read:
diff --git a/cps/editbooks.py b/cps/editbooks.py
index 20184bf9e8..d60150af29 100755
--- a/cps/editbooks.py
+++ b/cps/editbooks.py
@@ -695,6 +695,7 @@ def upload_cover(request, book):
                 abort(403)
             ret, message = helper.save_cover(requested_file, book.path)
             if ret is True:
+                helper.clear_cover_thumbnail_cache(book.id)
                 return True
             else:
                 flash(message, category="error")
@@ -818,6 +819,7 @@ def edit_book(book_id):
                         if result is True:
                             book.has_cover = 1
                             modif_date = True
+                            helper.clear_cover_thumbnail_cache(book.id)
                         else:
                             flash(error, category="error")
 
diff --git a/cps/fs.py b/cps/fs.py
new file mode 100644
index 0000000000..0171a5d51e
--- /dev/null
+++ b/cps/fs.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+#   This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+#     Copyright (C) 2020 mmonkey
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   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, see <http://www.gnu.org/licenses/>.
+
+from __future__ import division, print_function, unicode_literals
+from . import logger
+from .constants import CACHE_DIR
+from os import makedirs, remove
+from os.path import isdir, isfile, join
+from shutil import rmtree
+
+
+class FileSystem:
+    _instance = None
+    _cache_dir = CACHE_DIR
+
+    def __new__(cls):
+        if cls._instance is None:
+            cls._instance = super(FileSystem, cls).__new__(cls)
+            cls.log = logger.create()
+        return cls._instance
+
+    def get_cache_dir(self, cache_type=None):
+        if not isdir(self._cache_dir):
+            try:
+                makedirs(self._cache_dir)
+            except OSError:
+                self.log.info(f'Failed to create path {self._cache_dir} (Permission denied).')
+                return False
+
+        path = join(self._cache_dir, cache_type)
+        if cache_type and not isdir(path):
+            try:
+                makedirs(path)
+            except OSError:
+                self.log.info(f'Failed to create path {path} (Permission denied).')
+                return False
+
+        return path if cache_type else self._cache_dir
+
+    def get_cache_file_dir(self, filename, cache_type=None):
+        path = join(self.get_cache_dir(cache_type), filename[:2])
+        if not isdir(path):
+            try:
+                makedirs(path)
+            except OSError:
+                self.log.info(f'Failed to create path {path} (Permission denied).')
+                return False
+
+        return path
+
+    def get_cache_file_path(self, filename, cache_type=None):
+        return join(self.get_cache_file_dir(filename, cache_type), filename) if filename else None
+
+    def get_cache_file_exists(self, filename, cache_type=None):
+        path = self.get_cache_file_path(filename, cache_type)
+        return isfile(path)
+
+    def delete_cache_dir(self, cache_type=None):
+        if not cache_type and isdir(self._cache_dir):
+            try:
+                rmtree(self._cache_dir)
+            except OSError:
+                self.log.info(f'Failed to delete path {self._cache_dir} (Permission denied).')
+                return False
+
+        path = join(self._cache_dir, cache_type)
+        if cache_type and isdir(path):
+            try:
+                rmtree(path)
+            except OSError:
+                self.log.info(f'Failed to delete path {path} (Permission denied).')
+                return False
+
+    def delete_cache_file(self, filename, cache_type=None):
+        path = self.get_cache_file_path(filename, cache_type)
+        if isfile(path):
+            try:
+                remove(path)
+            except OSError:
+                self.log.info(f'Failed to delete path {path} (Permission denied).')
+                return False
diff --git a/cps/helper.py b/cps/helper.py
index 2daf8494e6..0081f3272a 100644
--- a/cps/helper.py
+++ b/cps/helper.py
@@ -34,7 +34,7 @@
 from flask import send_from_directory, make_response, redirect, abort, url_for
 from flask_babel import gettext as _
 from flask_login import current_user
-from sqlalchemy.sql.expression import true, false, and_, text, func
+from sqlalchemy.sql.expression import true, false, and_, or_, text, func
 from werkzeug.datastructures import Headers
 from werkzeug.security import generate_password_hash
 from markupsafe import escape
@@ -48,12 +48,14 @@
 
 from . import calibre_db, cli
 from .tasks.convert import TaskConvert
-from . import logger, config, get_locale, db, ub, kobo_sync_status
+from . import logger, config, get_locale, db, ub, kobo_sync_status, fs
 from . import gdriveutils as gd
-from .constants import STATIC_DIR as _STATIC_DIR
+from .constants import STATIC_DIR as _STATIC_DIR, CACHE_TYPE_THUMBNAILS, THUMBNAIL_TYPE_COVER, THUMBNAIL_TYPE_SERIES
 from .subproc_wrapper import process_wait
-from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
+from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS, STAT_ENDED, \
+    STAT_CANCELLED
 from .tasks.mail import TaskEmail
+from .tasks.thumbnail import TaskClearCoverThumbnailCache
 
 log = logger.create()
 
@@ -445,6 +447,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author, orignal_filepa
             return _("Error in rename file in path: %(error)s", error=str(ex))
     return False
 
+
 def update_dir_structure_gdrive(book_id, first_author, renamed_author):
     error = False
     book = calibre_db.get_book(book_id)
@@ -548,6 +551,7 @@ def uniq(inpt):
             output.append(x)
     return output
 
+
 def check_email(email):
     email = valid_email(email)
     if ub.session.query(ub.User).filter(func.lower(ub.User.email) == email.lower()).first():
@@ -594,6 +598,7 @@ def update_dir_structure(book_id,
 
 
 def delete_book(book, calibrepath, book_format):
+    clear_cover_thumbnail_cache(book.id)
     if config.config_use_google_drive:
         return delete_book_gdrive(book, book_format)
     else:
@@ -607,19 +612,29 @@ def get_cover_on_failure(use_generic_cover):
         return None
 
 
-def get_book_cover(book_id):
+def get_book_cover(book_id, resolution=None):
     book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
-    return get_book_cover_internal(book, use_generic_cover_on_failure=True)
+    return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
 
 
-def get_book_cover_with_uuid(book_uuid,
-                             use_generic_cover_on_failure=True):
+def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
     book = calibre_db.get_book_by_uuid(book_uuid)
     return get_book_cover_internal(book, use_generic_cover_on_failure)
 
 
-def get_book_cover_internal(book, use_generic_cover_on_failure):
+def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None):
     if book and book.has_cover:
+
+        # Send the book cover thumbnail if it exists in cache
+        if resolution:
+            thumbnail = get_book_cover_thumbnail(book, resolution)
+            if thumbnail:
+                cache = fs.FileSystem()
+                if cache.get_cache_file_exists(thumbnail.filename, CACHE_TYPE_THUMBNAILS):
+                    return send_from_directory(cache.get_cache_file_dir(thumbnail.filename, CACHE_TYPE_THUMBNAILS),
+                                               thumbnail.filename)
+
+        # Send the book cover from Google Drive if configured
         if config.config_use_google_drive:
             try:
                 if not gd.is_gdrive_ready():
@@ -633,6 +648,8 @@ def get_book_cover_internal(book, use_generic_cover_on_failure):
             except Exception as ex:
                 log.debug_or_exception(ex)
                 return get_cover_on_failure(use_generic_cover_on_failure)
+
+        # Send the book cover from the Calibre directory
         else:
             cover_file_path = os.path.join(config.config_calibre_dir, book.path)
             if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
@@ -643,6 +660,56 @@ def get_book_cover_internal(book, use_generic_cover_on_failure):
         return get_cover_on_failure(use_generic_cover_on_failure)
 
 
+def get_book_cover_thumbnail(book, resolution):
+    if book and book.has_cover:
+        return ub.session \
+            .query(ub.Thumbnail) \
+            .filter(ub.Thumbnail.type == THUMBNAIL_TYPE_COVER) \
+            .filter(ub.Thumbnail.entity_id == book.id) \
+            .filter(ub.Thumbnail.resolution == resolution) \
+            .filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
+            .first()
+
+
+def get_series_thumbnail_on_failure(series_id, resolution):
+    book = calibre_db.session \
+        .query(db.Books) \
+        .join(db.books_series_link) \
+        .join(db.Series) \
+        .filter(db.Series.id == series_id) \
+        .filter(db.Books.has_cover == 1) \
+        .first()
+
+    return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
+
+
+def get_series_cover_thumbnail(series_id, resolution=None):
+    return get_series_cover_internal(series_id, resolution)
+
+
+def get_series_cover_internal(series_id, resolution=None):
+    # Send the series thumbnail if it exists in cache
+    if resolution:
+        thumbnail = get_series_thumbnail(series_id, resolution)
+        if thumbnail:
+            cache = fs.FileSystem()
+            if cache.get_cache_file_exists(thumbnail.filename, CACHE_TYPE_THUMBNAILS):
+                return send_from_directory(cache.get_cache_file_dir(thumbnail.filename, CACHE_TYPE_THUMBNAILS),
+                                           thumbnail.filename)
+
+    return get_series_thumbnail_on_failure(series_id, resolution)
+
+
+def get_series_thumbnail(series_id, resolution):
+    return ub.session \
+        .query(ub.Thumbnail) \
+        .filter(ub.Thumbnail.type == THUMBNAIL_TYPE_SERIES) \
+        .filter(ub.Thumbnail.entity_id == series_id) \
+        .filter(ub.Thumbnail.resolution == resolution) \
+        .filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
+        .first()
+
+
 # saves book cover from url
 def save_cover_from_url(url, book_path):
     try:
@@ -835,12 +902,22 @@ def render_task_status(tasklist):
                     ret['status'] = _(u'Started')
                 elif task.stat == STAT_FINISH_SUCCESS:
                     ret['status'] = _(u'Finished')
+                elif task.stat == STAT_ENDED:
+                    ret['status'] = _(u'Ended')
+                elif task.stat == STAT_CANCELLED:
+                    ret['status'] = _(u'Cancelled')
                 else:
                     ret['status'] = _(u'Unknown Status')
 
-            ret['taskMessage'] = "{}: {}".format(_(task.name), task.message)
+            ret['taskMessage'] = "{}: {}".format(_(task.name), task.message) if task.message else _(task.name)
             ret['progress'] = "{} %".format(int(task.progress * 100))
             ret['user'] = escape(user)  # prevent xss
+
+            # Hidden fields
+            ret['id'] = task.id
+            ret['stat'] = task.stat
+            ret['is_cancellable'] = task.is_cancellable
+
             renderedtasklist.append(ret)
 
     return renderedtasklist
@@ -909,3 +986,7 @@ def get_download_link(book_id, book_format, client):
         return do_download_file(book, book_format, client, data1, headers)
     else:
         abort(404)
+
+
+def clear_cover_thumbnail_cache(book_id):
+    WorkerThread.add(None, TaskClearCoverThumbnailCache(book_id))
diff --git a/cps/jinjia.py b/cps/jinjia.py
index 06e99141e5..f88b36a26c 100644
--- a/cps/jinjia.py
+++ b/cps/jinjia.py
@@ -31,8 +31,7 @@
 from flask_babel import get_locale
 from flask_login import current_user
 from markupsafe import escape
-from . import logger
-
+from . import constants, logger
 
 jinjia = Blueprint('jinjia', __name__)
 log = logger.create()
@@ -128,12 +127,55 @@ def formatseriesindex_filter(series_index):
             return series_index
     return 0
 
+
 @jinjia.app_template_filter('escapedlink')
 def escapedlink_filter(url, text):
     return "<a href='{}'>{}</a>".format(url, escape(text))
 
+
 @jinjia.app_template_filter('uuidfilter')
 def uuidfilter(var):
     return uuid4()
 
 
+@jinjia.app_template_filter('cache_timestamp')
+def cache_timestamp(rolling_period='month'):
+    if rolling_period == 'day':
+        return str(int(datetime.datetime.today().replace(hour=1, minute=1).timestamp()))
+    elif rolling_period == 'year':
+        return str(int(datetime.datetime.today().replace(day=1).timestamp()))
+    else:
+        return str(int(datetime.datetime.today().replace(month=1, day=1).timestamp()))
+
+
+@jinjia.app_template_filter('last_modified')
+def book_last_modified(book):
+    return str(int(book.last_modified.timestamp()))
+
+
+@jinjia.app_template_filter('get_cover_srcset')
+def get_cover_srcset(book):
+    srcset = list()
+    resolutions = {
+        constants.COVER_THUMBNAIL_SMALL: 'sm',
+        constants.COVER_THUMBNAIL_MEDIUM: 'md',
+        constants.COVER_THUMBNAIL_LARGE: 'lg'
+    }
+    for resolution, shortname in resolutions.items():
+        url = url_for('web.get_cover', book_id=book.id, resolution=shortname, c=book_last_modified(book))
+        srcset.append(f'{url} {resolution}x')
+    return ', '.join(srcset)
+
+
+@jinjia.app_template_filter('get_series_srcset')
+def get_cover_srcset(series):
+    srcset = list()
+    resolutions = {
+        constants.COVER_THUMBNAIL_SMALL: 'sm',
+        constants.COVER_THUMBNAIL_MEDIUM: 'md',
+        constants.COVER_THUMBNAIL_LARGE: 'lg'
+    }
+    for resolution, shortname in resolutions.items():
+        url = url_for('web.get_series_cover', series_id=series.id, resolution=shortname, c=cache_timestamp())
+        srcset.append(f'{url} {resolution}x')
+    return ', '.join(srcset)
diff --git a/cps/schedule.py b/cps/schedule.py
new file mode 100644
index 0000000000..5b97cecb32
--- /dev/null
+++ b/cps/schedule.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+#   This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+#     Copyright (C) 2020 mmonkey
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   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, see <http://www.gnu.org/licenses/>.
+
+import datetime
+
+from . import config, constants
+from .services.background_scheduler import BackgroundScheduler
+from .tasks.database import TaskReconnectDatabase
+from .tasks.thumbnail import TaskGenerateCoverThumbnails, TaskGenerateSeriesThumbnails
+from .services.worker import WorkerThread
+
+
+def get_scheduled_tasks(reconnect=True):
+    tasks = list()
+
+    # Reconnect Calibre database (metadata.db)
+    if reconnect:
+        tasks.append(lambda: TaskReconnectDatabase())
+
+    # Generate all missing book cover thumbnails
+    if config.schedule_generate_book_covers:
+        tasks.append(lambda: TaskGenerateCoverThumbnails())
+
+    # Generate all missing series thumbnails
+    if config.schedule_generate_series_covers:
+        tasks.append(lambda: TaskGenerateSeriesThumbnails())
+
+    return tasks
+
+
+def end_scheduled_tasks():
+    worker = WorkerThread.get_instance()
+    for __, __, __, task in worker.tasks:
+        if task.scheduled and task.is_cancellable:
+            worker.end_task(task.id)
+
+
+def register_scheduled_tasks():
+    scheduler = BackgroundScheduler()
+
+    if scheduler:
+        # Remove all existing jobs
+        scheduler.remove_all_jobs()
+
+        start = config.schedule_start_time
+        end = config.schedule_end_time
+
+        # Register scheduled tasks
+        if start != end:
+            scheduler.schedule_tasks(tasks=get_scheduled_tasks(), trigger='cron', hour=start)
+            scheduler.schedule(func=end_scheduled_tasks, trigger='cron', hour=end)
+
+        # Kick-off tasks, if they should currently be running
+        if should_task_be_running(start, end):
+            scheduler.schedule_tasks_immediately(tasks=get_scheduled_tasks(False))
+
+
+def register_startup_tasks():
+    scheduler = BackgroundScheduler()
+
+    if scheduler:
+        start = config.schedule_start_time
+        end = config.schedule_end_time
+
+        # Run scheduled tasks immediately for development and testing
+        # Ignore tasks that should currently be running, as these will be added when registering scheduled tasks
+        if constants.APP_MODE in ['development', 'test'] and not should_task_be_running(start, end):
+            scheduler.schedule_tasks_immediately(tasks=get_scheduled_tasks(False))
+
+
+def should_task_be_running(start, end):
+    now = datetime.datetime.now().hour
+    return (start < end and start <= now < end) or (end < start <= now or now < end)
diff --git a/cps/services/background_scheduler.py b/cps/services/background_scheduler.py
new file mode 100644
index 0000000000..415b296250
--- /dev/null
+++ b/cps/services/background_scheduler.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+#   This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+#     Copyright (C) 2020 mmonkey
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   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, see <http://www.gnu.org/licenses/>.
+
+import atexit
+
+from .. import logger
+from .worker import WorkerThread
+
+try:
+    from apscheduler.schedulers.background import BackgroundScheduler as BScheduler
+    use_APScheduler = True
+except (ImportError, RuntimeError) as e:
+    use_APScheduler = False
+    log = logger.create()
+    log.info('APScheduler not found. Unable to schedule tasks.')
+
+
+class BackgroundScheduler:
+    _instance = None
+
+    def __new__(cls):
+        if not use_APScheduler:
+            return False
+
+        if cls._instance is None:
+            cls._instance = super(BackgroundScheduler, cls).__new__(cls)
+            cls.log = logger.create()
+            cls.scheduler = BScheduler()
+            cls.scheduler.start()
+
+            atexit.register(lambda: cls.scheduler.shutdown())
+
+        return cls._instance
+
+    def schedule(self, func, trigger, **trigger_args):
+        if use_APScheduler:
+            return self.scheduler.add_job(func=func, trigger=trigger, **trigger_args)
+
+    # Expects a lambda expression for the task
+    def schedule_task(self, task, user=None, trigger='cron', **trigger_args):
+        if use_APScheduler:
+            def scheduled_task():
+                worker_task = task()
+                worker_task.scheduled = True
+                WorkerThread.add(user, worker_task)
+            return self.schedule(func=scheduled_task, trigger=trigger, **trigger_args)
+
+    # Expects a list of lambda expressions for the tasks
+    def schedule_tasks(self, tasks, user=None, trigger='cron', **trigger_args):
+        if use_APScheduler:
+            for task in tasks:
+                self.schedule_task(task, user=user, trigger=trigger, **trigger_args)
+
+    # Expects a lambda expression for the task
+    def schedule_task_immediately(self, task, user=None):
+        if use_APScheduler:
+            def immediate_task():
+                WorkerThread.add(user, task())
+            return self.schedule(func=immediate_task, trigger='date')
+
+    # Expects a list of lambda expressions for the tasks
+    def schedule_tasks_immediately(self, tasks, user=None):
+        if use_APScheduler:
+            for task in tasks:
+                self.schedule_task_immediately(task, user)
+
+    # Remove all jobs
+    def remove_all_jobs(self):
+        self.scheduler.remove_all_jobs()
diff --git a/cps/services/worker.py b/cps/services/worker.py
index 076c91048a..7e0f8a5c84 100644
--- a/cps/services/worker.py
+++ b/cps/services/worker.py
@@ -37,6 +37,8 @@
 STAT_FAIL = 1
 STAT_STARTED = 2
 STAT_FINISH_SUCCESS = 3
+STAT_ENDED = 4
+STAT_CANCELLED = 5
 
 # Only retain this many tasks in dequeued list
 TASK_CLEANUP_TRIGGER = 20
@@ -51,7 +53,6 @@ def _get_main_thread():
     raise Exception("main thread not found?!")
 
 
-
 class ImprovedQueue(queue.Queue):
     def to_list(self):
         """
@@ -61,12 +62,13 @@ def to_list(self):
         with self.mutex:
             return list(self.queue)
 
+
 # Class for all worker tasks in the background
 class WorkerThread(threading.Thread):
     _instance = None
 
     @classmethod
-    def getInstance(cls):
+    def get_instance(cls):
         if cls._instance is None:
             cls._instance = WorkerThread()
         return cls._instance
@@ -83,12 +85,13 @@ def __init__(self):
 
     @classmethod
     def add(cls, user, task):
-        ins = cls.getInstance()
+        ins = cls.get_instance()
         ins.num += 1
-        log.debug("Add Task for user: {} - {}".format(user, task))
+        username = user if user is not None else 'System'
+        log.debug("Add Task for user: {} - {}".format(username, task))
         ins.queue.put(QueuedTask(
             num=ins.num,
-            user=user,
+            user=username,
             added=datetime.now(),
             task=task,
         ))
@@ -144,8 +147,18 @@ def run(self):
                 # CalibreTask.start() should wrap all exceptions in it's own error handling
                 item.task.start(self)
 
+            # remove self_cleanup tasks from list
+            if item.task.self_cleanup:
+                self.dequeued.remove(item)
+
             self.queue.task_done()
 
+    def end_task(self, task_id):
+        ins = self.get_instance()
+        for __, __, __, task in ins.tasks:
+            if str(task.id) == str(task_id) and task.is_cancellable:
+                task.stat = STAT_CANCELLED if task.stat == STAT_WAITING else STAT_ENDED
+
 
 class CalibreTask:
     __metaclass__ = abc.ABCMeta
@@ -158,10 +171,12 @@ def __init__(self, message):
         self.end_time = None
         self.message = message
         self.id = uuid.uuid4()
+        self.self_cleanup = False
+        self._scheduled = False
 
     @abc.abstractmethod
     def run(self, worker_thread):
-        """Provides the caller some human-readable name for this class"""
+        """The main entry-point for this task"""
         raise NotImplementedError
 
     @abc.abstractmethod
@@ -169,6 +184,11 @@ def name(self):
         """Provides the caller some human-readable name for this class"""
         raise NotImplementedError
 
+    @abc.abstractmethod
+    def is_cancellable(self):
+        """Does this task gracefully handle being cancelled (STAT_ENDED, STAT_CANCELLED)?"""
+        raise NotImplementedError
+
     def start(self, *args):
         self.start_time = datetime.now()
         self.stat = STAT_STARTED
@@ -219,7 +239,7 @@ def dead(self):
         We have a separate dictating this because there may be certain tasks that want to override this
         """
         # By default, we're good to clean a task if it's "Done"
-        return self.stat in (STAT_FINISH_SUCCESS, STAT_FAIL)
+        return self.stat in (STAT_FINISH_SUCCESS, STAT_FAIL, STAT_ENDED, STAT_CANCELLED)
 
     '''@progress.setter
     def progress(self, x):        
@@ -229,6 +249,22 @@ def progress(self, x):
             x = 0
         self._progress = x'''
 
+    @property
+    def self_cleanup(self):
+        return self._self_cleanup
+
+    @self_cleanup.setter
+    def self_cleanup(self, is_self_cleanup):
+        self._self_cleanup = is_self_cleanup
+
+    @property
+    def scheduled(self):
+        return self._scheduled
+
+    @scheduled.setter
+    def scheduled(self, is_scheduled):
+        self._scheduled = is_scheduled
+
     def _handleError(self, error_message):
         self.stat = STAT_FAIL
         self.progress = 1
diff --git a/cps/static/css/caliBlur.css b/cps/static/css/caliBlur.css
index 3a98018080..b2b3542347 100644
--- a/cps/static/css/caliBlur.css
+++ b/cps/static/css/caliBlur.css
@@ -5150,7 +5150,7 @@ body.login > div.navbar.navbar-default.navbar-static-top > div > div.navbar-head
     pointer-events: none
 }
 
-#DeleteDomain:hover:before, #RestartDialog:hover:before, #ShutdownDialog:hover:before, #StatusDialog:hover:before, #deleteButton, #deleteModal:hover:before, body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover td > a:hover {
+#DeleteDomain:hover:before, #RestartDialog:hover:before, #ShutdownDialog:hover:before, #StatusDialog:hover:before,  #deleteButton, #deleteModal:hover:before, #cancelTaskModal:hover:before, body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover td > a:hover {
     cursor: pointer
 }
 
@@ -5237,7 +5237,11 @@ body.admin > div.container-fluid > div > div.col-sm-10 > div.container-fluid > d
     margin-bottom: 20px
 }
 
-body.admin:not(.modal-open) .btn-default {
+body.admin > div.container-fluid div.scheduled_tasks_details {
+    margin-bottom: 20px
+}
+
+body.admin .btn-default {
     margin-bottom: 10px
 }
 
@@ -5468,7 +5472,7 @@ body.admin.modal-open .navbar {
     z-index: 0 !important
 }
 
-#RestartDialog, #ShutdownDialog, #StatusDialog, #deleteModal {
+#RestartDialog, #ShutdownDialog, #StatusDialog, #deleteModal, #cancelTaskModal {
     top: 0;
     overflow: hidden;
     padding-top: 70px;
@@ -5478,7 +5482,7 @@ body.admin.modal-open .navbar {
     background: rgba(0, 0, 0, .5)
 }
 
-#RestartDialog:before, #ShutdownDialog:before, #StatusDialog:before, #deleteModal:before {
+#RestartDialog:before, #ShutdownDialog:before, #StatusDialog:before, #deleteModal:before, #cancelTaskModal:before {
     content: "\E208";
     padding-right: 10px;
     display: block;
@@ -5500,18 +5504,18 @@ body.admin.modal-open .navbar {
     z-index: 99
 }
 
-#RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #deleteModal.in:before {
+#RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #deleteModal.in:before, #cancelTaskModal.in:before {
     -webkit-transform: translate(0, 0);
     -ms-transform: translate(0, 0);
     transform: translate(0, 0)
 }
 
-#RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #deleteModal > .modal-dialog {
+#RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #deleteModal > .modal-dialog, #cancelTaskModal > .modal-dialog {
     width: 450px;
     margin: auto
 }
 
-#RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content {
+#RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content, #cancelTaskModal > .modal-dialog > .modal-content {
     max-height: calc(100% - 90px);
     -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
     box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
@@ -5522,7 +5526,7 @@ body.admin.modal-open .navbar {
     width: 450px
 }
 
-#RestartDialog > .modal-dialog > .modal-content > .modal-header, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header, #StatusDialog > .modal-dialog > .modal-content > .modal-header, #deleteModal > .modal-dialog > .modal-content > .modal-header {
+#RestartDialog > .modal-dialog > .modal-content > .modal-header, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header, #StatusDialog > .modal-dialog > .modal-content > .modal-header, #deleteModal > .modal-dialog > .modal-content > .modal-header, #cancelTaskModal > .modal-dialog > .modal-content > .modal-header {
     padding: 15px 20px;
     border-radius: 3px 3px 0 0;
     line-height: 1.71428571;
@@ -5535,7 +5539,7 @@ body.admin.modal-open .navbar {
     text-align: left
 }
 
-#RestartDialog > .modal-dialog > .modal-content > .modal-header:before, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header:before, #StatusDialog > .modal-dialog > .modal-content > .modal-header:before, #deleteModal > .modal-dialog > .modal-content > .modal-header:before {
+#RestartDialog > .modal-dialog > .modal-content > .modal-header:before, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header:before, #StatusDialog > .modal-dialog > .modal-content > .modal-header:before, #deleteModal > .modal-dialog > .modal-content > .modal-header:before, #cancelTaskModal > .modal-dialog > .modal-content > .modal-header:before {
     padding-right: 10px;
     font-size: 18px;
     color: #999;
@@ -5564,6 +5568,11 @@ body.admin.modal-open .navbar {
     font-family: plex-icons-new, serif
 }
 
+#cancelTaskModal > .modal-dialog > .modal-content > .modal-header:before {
+    content: "\EA6D";
+    font-family: plex-icons-new, serif
+}
+
 #RestartDialog > .modal-dialog > .modal-content > .modal-header:after {
     content: "Restart Calibre-Web";
     display: inline-block;
@@ -5588,7 +5597,13 @@ body.admin.modal-open .navbar {
     font-size: 20px
 }
 
-#StatusDialog > .modal-dialog > .modal-content > .modal-header > span, #deleteModal > .modal-dialog > .modal-content > .modal-header > span, #loader > center > img, .rating-mobile {
+#cancelTaskModal > .modal-dialog > .modal-content > .modal-header:after {
+    content: "Delete Book";
+    display: inline-block;
+    font-size: 20px
+}
+
+#StatusDialog > .modal-dialog > .modal-content > .modal-header > span, #deleteModal > .modal-dialog > .modal-content > .modal-header > span, #cancelTaskModal > .modal-dialog > .modal-content > .modal-header > span, #loader > center > img, .rating-mobile {
     display: none
 }
 
@@ -5602,7 +5617,7 @@ body.admin.modal-open .navbar {
     text-align: left
 }
 
-#ShutdownDialog > .modal-dialog > .modal-content > .modal-body, #StatusDialog > .modal-dialog > .modal-content > .modal-body, #deleteModal > .modal-dialog > .modal-content > .modal-body {
+#ShutdownDialog > .modal-dialog > .modal-content > .modal-body, #StatusDialog > .modal-dialog > .modal-content > .modal-body, #deleteModal > .modal-dialog > .modal-content > .modal-body, #cancelTaskModal > .modal-dialog > .modal-content > .modal-body {
     padding: 20px 20px 40px;
     font-size: 16px;
     line-height: 1.6em;
@@ -5612,7 +5627,7 @@ body.admin.modal-open .navbar {
     text-align: left
 }
 
-#RestartDialog > .modal-dialog > .modal-content > .modal-body > p, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > p, #StatusDialog > .modal-dialog > .modal-content > .modal-body > p, #deleteModal > .modal-dialog > .modal-content > .modal-body > p {
+#RestartDialog > .modal-dialog > .modal-content > .modal-body > p, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > p, #StatusDialog > .modal-dialog > .modal-content > .modal-body > p, #deleteModal > .modal-dialog > .modal-content > .modal-body > p, #cancelTaskModal > .modal-dialog > .modal-content > .modal-body > p {
     padding: 20px 20px 0 0;
     font-size: 16px;
     line-height: 1.6em;
@@ -5621,7 +5636,7 @@ body.admin.modal-open .navbar {
     background: #282828
 }
 
-#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart), #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown), #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
+#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart), #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown), #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default, #cancelTaskModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
     float: right;
     z-index: 9;
     position: relative;
@@ -5669,6 +5684,18 @@ body.admin.modal-open .navbar {
     border-radius: 3px
 }
 
+#cancelTaskModal > .modal-dialog > .modal-content > .modal-footer > .btn-danger {
+    float: right;
+    z-index: 9;
+    position: relative;
+    margin: 0 0 0 10px;
+    min-width: 80px;
+    padding: 10px 18px;
+    font-size: 16px;
+    line-height: 1.33;
+    border-radius: 3px
+}
+
 #RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart) {
     margin: 25px 0 0 10px
 }
@@ -5681,7 +5708,11 @@ body.admin.modal-open .navbar {
     margin: 0 0 0 10px
 }
 
-#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart):hover, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown):hover, #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default:hover {
+#cancelTaskModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
+    margin: 0 0 0 10px
+}
+
+#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart):hover, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown):hover, #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default:hover, #cancelTaskModal > .modal-dialog > .modal-content > .modal-footer > .btn-default:hover {
     background-color: hsla(0, 0%, 100%, .3)
 }
 
@@ -7303,11 +7334,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
         background-color: transparent !important
     }
 
-    #RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #deleteModal > .modal-dialog {
+    #RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #deleteModal > .modal-dialog, #cancelTaskModal > .modal-dialog {
         max-width: calc(100vw - 40px)
     }
 
-    #RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content {
+    #RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content, #cancelTaskModal > .modal-dialog > .modal-content {
         max-width: calc(100vw - 40px);
         left: 0
     }
@@ -7457,7 +7488,7 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
         padding: 30px 15px
     }
 
-    #RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #deleteModal.in:before {
+    #RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #deleteModal.in:before, #cancelTaskModal.in:before {
         left: auto;
         right: 34px
     }
diff --git a/cps/static/js/table.js b/cps/static/js/table.js
index dce1d06c3c..4cafe05dfa 100644
--- a/cps/static/js/table.js
+++ b/cps/static/js/table.js
@@ -15,7 +15,7 @@
  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* exported TableActions, RestrictionActions, EbookActions, responseHandler */
+/* exported TableActions, RestrictionActions, EbookActions, TaskActions, responseHandler */
 /* global getPath, confirmDialog */
 
 var selections = [];
@@ -42,6 +42,24 @@ $(function() {
         }, 1000);
     }
 
+    $("#cancel_task_confirm").click(function() {
+        //get data-id attribute of the clicked element
+        var taskId = $(this).data("task-id");
+        $.ajax({
+            method: "post",
+            contentType: "application/json; charset=utf-8",
+            dataType: "json",
+            url: window.location.pathname + "/../ajax/canceltask",
+            data: JSON.stringify({"task_id": taskId}),
+        });
+    });
+    //triggered when modal is about to be shown
+    $("#cancelTaskModal").on("show.bs.modal", function(e) {
+        //get data-id attribute of the clicked element and store in button
+        var taskId = $(e.relatedTarget).data("task-id");
+        $(e.currentTarget).find("#cancel_task_confirm").data("task-id", taskId);
+    });
+
     $("#books-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table",
         function (e, rowsAfter, rowsBefore) {
             var rows = rowsAfter;
@@ -581,6 +599,7 @@ function handle_header_buttons () {
         $(".header_select").removeAttr("disabled");
     }
 }
+
 /* Function for deleting domain restrictions */
 function TableActions (value, row) {
     return [
@@ -618,6 +637,19 @@ function UserActions (value, row) {
     ].join("");
 }
 
+/* Function for cancelling tasks */
+function TaskActions (value, row) {
+    var cancellableStats = [0, 1, 2];
+    if (row.id && row.is_cancellable && cancellableStats.includes(row.stat)) {
+        return [
+            "<div class=\"task-cancel\" data-toggle=\"modal\" data-target=\"#cancelTaskModal\" data-task-id=\"" + row.id + "\" title=\"Cancel\">",
+            "<i class=\"glyphicon glyphicon-ban-circle\"></i>",
+            "</div>"
+        ].join("");
+    }
+    return '';
+}
+
 /* Function for keeping checked rows */
 function responseHandler(res) {
     $.each(res.rows, function (i, row) {
diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py
index 596bf95c06..f8df79325c 100644
--- a/cps/tasks/convert.py
+++ b/cps/tasks/convert.py
@@ -255,3 +255,7 @@ def name(self):
 
     def __str__(self):
         return "Convert {} {}".format(self.bookid, self.kindle_mail)
+
+    @property
+    def is_cancellable(self):
+        return False
diff --git a/cps/tasks/database.py b/cps/tasks/database.py
new file mode 100644
index 0000000000..0441d5641f
--- /dev/null
+++ b/cps/tasks/database.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+#   This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+#     Copyright (C) 2020 mmonkey
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   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, see <http://www.gnu.org/licenses/>.
+
+from __future__ import division, print_function, unicode_literals
+
+from cps import config, logger
+from cps.services.worker import CalibreTask
+
+try:
+    from urllib.request import urlopen
+except ImportError as e:
+    from urllib2 import urlopen
+
+
+class TaskReconnectDatabase(CalibreTask):
+    def __init__(self, task_message=u'Reconnecting Calibre database'):
+        super(TaskReconnectDatabase, self).__init__(task_message)
+        self.log = logger.create()
+        self.listen_address = config.get_config_ipaddress()
+        self.listen_port = config.config_port
+
+    def run(self, worker_thread):
+        address = self.listen_address if self.listen_address else 'localhost'
+        port = self.listen_port if self.listen_port else 8083
+
+        try:
+            urlopen('http://' + address + ':' + str(port) + '/reconnect')
+            self._handleSuccess()
+        except Exception as ex:
+            self._handleError(u'Unable to reconnect Calibre database: ' + str(ex))
+
+    @property
+    def name(self):
+        return "Reconnect Database"
+
+    @property
+    def is_cancellable(self):
+        return False
diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py
index 03526c8bdc..4cfc742aa2 100644
--- a/cps/tasks/mail.py
+++ b/cps/tasks/mail.py
@@ -266,5 +266,9 @@ def _get_attachment(cls, bookpath, filename):
     def name(self):
         return "E-mail"
 
+    @property
+    def is_cancellable(self):
+        return False
+
     def __str__(self):
         return "E-mail {}, {}".format(self.name, self.subject)
diff --git a/cps/tasks/thumbnail.py b/cps/tasks/thumbnail.py
new file mode 100644
index 0000000000..d147f10d87
--- /dev/null
+++ b/cps/tasks/thumbnail.py
@@ -0,0 +1,472 @@
+# -*- coding: utf-8 -*-
+
+#   This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+#     Copyright (C) 2020 monkey
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   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, see <http://www.gnu.org/licenses/>.
+
+from __future__ import division, print_function, unicode_literals
+import os
+
+from .. import constants
+from cps import config, db, fs, gdriveutils, logger, ub
+from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED
+from datetime import datetime
+from sqlalchemy import func, text, or_
+
+try:
+    from urllib.request import urlopen
+except ImportError as e:
+    from urllib2 import urlopen
+
+try:
+    from wand.image import Image
+    use_IM = True
+except (ImportError, RuntimeError) as e:
+    use_IM = False
+
+
+def get_resize_height(resolution):
+    return int(225 * resolution)
+
+
+def get_resize_width(resolution, original_width, original_height):
+    height = get_resize_height(resolution)
+    percent = (height / float(original_height))
+    width = int((float(original_width) * float(percent)))
+    return width if width % 2 == 0 else width + 1
+
+
+def get_best_fit(width, height, image_width, image_height):
+    resize_width = int(width / 2.0)
+    resize_height = int(height / 2.0)
+    aspect_ratio = image_width / image_height
+
+    # If this image's aspect ratio is different than the first image, then resize this image
+    # to fill the width and height of the first image
+    if aspect_ratio < width / height:
+        resize_width = int(width / 2.0)
+        resize_height = image_height * int(width / 2.0) / image_width
+
+    elif aspect_ratio > width / height:
+        resize_width = image_width * int(height / 2.0) / image_height
+        resize_height = int(height / 2.0)
+
+    return {'width': resize_width, 'height': resize_height}
+
+
+class TaskGenerateCoverThumbnails(CalibreTask):
+    def __init__(self, task_message=''):
+        super(TaskGenerateCoverThumbnails, self).__init__(task_message)
+        self.log = logger.create()
+        self.app_db_session = ub.get_new_session_instance()
+        self.calibre_db = db.CalibreDB(expire_on_commit=False)
+        self.cache = fs.FileSystem()
+        self.resolutions = [
+            constants.COVER_THUMBNAIL_SMALL,
+            constants.COVER_THUMBNAIL_MEDIUM
+        ]
+
+    def run(self, worker_thread):
+        if self.calibre_db.session and use_IM and self.stat != STAT_CANCELLED and self.stat != STAT_ENDED:
+            self.message = 'Scanning Books'
+            books_with_covers = self.get_books_with_covers()
+            count = len(books_with_covers)
+
+            total_generated = 0
+            for i, book in enumerate(books_with_covers):
+                generated = 0
+                book_cover_thumbnails = self.get_book_cover_thumbnails(book.id)
+
+                # Generate new thumbnails for missing covers
+                resolutions = list(map(lambda t: t.resolution, book_cover_thumbnails))
+                missing_resolutions = list(set(self.resolutions).difference(resolutions))
+                for resolution in missing_resolutions:
+                    generated += 1
+                    self.create_book_cover_thumbnail(book, resolution)
+
+                # Replace outdated or missing thumbnails
+                for thumbnail in book_cover_thumbnails:
+                    if book.last_modified > thumbnail.generated_at:
+                        generated += 1
+                        self.update_book_cover_thumbnail(book, thumbnail)
+
+                    elif not self.cache.get_cache_file_exists(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS):
+                        generated += 1
+                        self.update_book_cover_thumbnail(book, thumbnail)
+
+                # Increment the progress
+                self.progress = (1.0 / count) * i
+
+                if generated > 0:
+                    total_generated += generated
+                    self.message = u'Generated {0} cover thumbnails'.format(total_generated)
+
+                # Check if job has been cancelled or ended
+                if self.stat == STAT_CANCELLED:
+                    self.log.info(f'GenerateCoverThumbnails task has been cancelled.')
+                    return
+
+                if self.stat == STAT_ENDED:
+                    self.log.info(f'GenerateCoverThumbnails task has been ended.')
+                    return
+
+            if total_generated == 0:
+                self.self_cleanup = True
+
+        self._handleSuccess()
+        self.app_db_session.remove()
+
+    def get_books_with_covers(self):
+        return self.calibre_db.session \
+            .query(db.Books) \
+            .filter(db.Books.has_cover == 1) \
+            .all()
+
+    def get_book_cover_thumbnails(self, book_id):
+        return self.app_db_session \
+            .query(ub.Thumbnail) \
+            .filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_COVER) \
+            .filter(ub.Thumbnail.entity_id == book_id) \
+            .filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
+            .all()
+
+    def create_book_cover_thumbnail(self, book, resolution):
+        thumbnail = ub.Thumbnail()
+        thumbnail.type = constants.THUMBNAIL_TYPE_COVER
+        thumbnail.entity_id = book.id
+        thumbnail.format = 'jpeg'
+        thumbnail.resolution = resolution
+
+        self.app_db_session.add(thumbnail)
+        try:
+            self.app_db_session.commit()
+            self.generate_book_thumbnail(book, thumbnail)
+        except Exception as ex:
+            self.log.info(u'Error creating book thumbnail: ' + str(ex))
+            self._handleError(u'Error creating book thumbnail: ' + str(ex))
+            self.app_db_session.rollback()
+
+    def update_book_cover_thumbnail(self, book, thumbnail):
+        thumbnail.generated_at = datetime.utcnow()
+
+        try:
+            self.app_db_session.commit()
+            self.cache.delete_cache_file(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
+            self.generate_book_thumbnail(book, thumbnail)
+        except Exception as ex:
+            self.log.info(u'Error updating book thumbnail: ' + str(ex))
+            self._handleError(u'Error updating book thumbnail: ' + str(ex))
+            self.app_db_session.rollback()
+
+    def generate_book_thumbnail(self, book, thumbnail):
+        if book and thumbnail:
+            if config.config_use_google_drive:
+                if not gdriveutils.is_gdrive_ready():
+                    raise Exception('Google Drive is configured but not ready')
+
+                web_content_link = gdriveutils.get_cover_via_gdrive(book.path)
+                if not web_content_link:
+                    raise Exception('Google Drive cover url not found')
+
+                stream = None
+                try:
+                    stream = urlopen(web_content_link)
+                    with Image(file=stream) as img:
+                        height = get_resize_height(thumbnail.resolution)
+                        if img.height > height:
+                            width = get_resize_width(thumbnail.resolution, img.width, img.height)
+                            img.resize(width=width, height=height, filter='lanczos')
+                            img.format = thumbnail.format
+                            filename = self.cache.get_cache_file_path(thumbnail.filename,
+                                                                      constants.CACHE_TYPE_THUMBNAILS)
+                            img.save(filename=filename)
+                except Exception as ex:
+                    # Bubble exception to calling function
+                    self.log.info(u'Error generating thumbnail file: ' + str(ex))
+                    raise ex
+                finally:
+                    if stream is not None:
+                        stream.close()
+            else:
+                book_cover_filepath = os.path.join(config.config_calibre_dir, book.path, 'cover.jpg')
+                if not os.path.isfile(book_cover_filepath):
+                    raise Exception('Book cover file not found')
+
+                with Image(filename=book_cover_filepath) as img:
+                    height = get_resize_height(thumbnail.resolution)
+                    if img.height > height:
+                        width = get_resize_width(thumbnail.resolution, img.width, img.height)
+                        img.resize(width=width, height=height, filter='lanczos')
+                        img.format = thumbnail.format
+                        filename = self.cache.get_cache_file_path(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
+                        img.save(filename=filename)
+
+    @property
+    def name(self):
+        return 'GenerateCoverThumbnails'
+
+    @property
+    def is_cancellable(self):
+        return True
+
+
+class TaskGenerateSeriesThumbnails(CalibreTask):
+    def __init__(self, task_message=''):
+        super(TaskGenerateSeriesThumbnails, self).__init__(task_message)
+        self.log = logger.create()
+        self.app_db_session = ub.get_new_session_instance()
+        self.calibre_db = db.CalibreDB(expire_on_commit=False)
+        self.cache = fs.FileSystem()
+        self.resolutions = [
+            constants.COVER_THUMBNAIL_SMALL,
+            constants.COVER_THUMBNAIL_MEDIUM,
+        ]
+
+    def run(self, worker_thread):
+        if self.calibre_db.session and use_IM and self.stat != STAT_CANCELLED and self.stat != STAT_ENDED:
+            self.message = 'Scanning Series'
+            all_series = self.get_series_with_four_plus_books()
+            count = len(all_series)
+
+            total_generated = 0
+            for i, series in enumerate(all_series):
+                generated = 0
+                series_thumbnails = self.get_series_thumbnails(series.id)
+                series_books = self.get_series_books(series.id)
+
+                # Generate new thumbnails for missing covers
+                resolutions = list(map(lambda t: t.resolution, series_thumbnails))
+                missing_resolutions = list(set(self.resolutions).difference(resolutions))
+                for resolution in missing_resolutions:
+                    generated += 1
+                    self.create_series_thumbnail(series, series_books, resolution)
+
+                # Replace outdated or missing thumbnails
+                for thumbnail in series_thumbnails:
+                    if any(book.last_modified > thumbnail.generated_at for book in series_books):
+                        generated += 1
+                        self.update_series_thumbnail(series_books, thumbnail)
+
+                    elif not self.cache.get_cache_file_exists(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS):
+                        generated += 1
+                        self.update_series_thumbnail(series_books, thumbnail)
+
+                # Increment the progress
+                self.progress = (1.0 / count) * i
+
+                if generated > 0:
+                    total_generated += generated
+                    self.message = u'Generated {0} series thumbnails'.format(total_generated)
+
+                # Check if job has been cancelled or ended
+                if self.stat == STAT_CANCELLED:
+                    self.log.info(f'GenerateSeriesThumbnails task has been cancelled.')
+                    return
+
+                if self.stat == STAT_ENDED:
+                    self.log.info(f'GenerateSeriesThumbnails task has been ended.')
+                    return
+
+            if total_generated == 0:
+                self.self_cleanup = True
+
+        self._handleSuccess()
+        self.app_db_session.remove()
+
+    def get_series_with_four_plus_books(self):
+        return self.calibre_db.session \
+            .query(db.Series) \
+            .join(db.books_series_link) \
+            .join(db.Books) \
+            .filter(db.Books.has_cover == 1) \
+            .group_by(text('books_series_link.series')) \
+            .having(func.count('book_series_link') > 3) \
+            .all()
+
+    def get_series_books(self, series_id):
+        return self.calibre_db.session \
+            .query(db.Books) \
+            .join(db.books_series_link) \
+            .join(db.Series) \
+            .filter(db.Books.has_cover == 1) \
+            .filter(db.Series.id == series_id) \
+            .all()
+
+    def get_series_thumbnails(self, series_id):
+        return self.app_db_session \
+            .query(ub.Thumbnail) \
+            .filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_SERIES) \
+            .filter(ub.Thumbnail.entity_id == series_id) \
+            .filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
+            .all()
+
+    def create_series_thumbnail(self, series, series_books, resolution):
+        thumbnail = ub.Thumbnail()
+        thumbnail.type = constants.THUMBNAIL_TYPE_SERIES
+        thumbnail.entity_id = series.id
+        thumbnail.format = 'jpeg'
+        thumbnail.resolution = resolution
+
+        self.app_db_session.add(thumbnail)
+        try:
+            self.app_db_session.commit()
+            self.generate_series_thumbnail(series_books, thumbnail)
+        except Exception as ex:
+            self.log.info(u'Error creating book thumbnail: ' + str(ex))
+            self._handleError(u'Error creating book thumbnail: ' + str(ex))
+            self.app_db_session.rollback()
+
+    def update_series_thumbnail(self, series_books, thumbnail):
+        thumbnail.generated_at = datetime.utcnow()
+
+        try:
+            self.app_db_session.commit()
+            self.cache.delete_cache_file(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
+            self.generate_series_thumbnail(series_books, thumbnail)
+        except Exception as ex:
+            self.log.info(u'Error updating book thumbnail: ' + str(ex))
+            self._handleError(u'Error updating book thumbnail: ' + str(ex))
+            self.app_db_session.rollback()
+
+    def generate_series_thumbnail(self, series_books, thumbnail):
+        # Get the last four books in the series based on series_index
+        books = sorted(series_books, key=lambda b: float(b.series_index), reverse=True)[:4]
+
+        top = 0
+        left = 0
+        width = 0
+        height = 0
+        with Image() as canvas:
+            for book in books:
+                if config.config_use_google_drive:
+                    if not gdriveutils.is_gdrive_ready():
+                        raise Exception('Google Drive is configured but not ready')
+
+                    web_content_link = gdriveutils.get_cover_via_gdrive(book.path)
+                    if not web_content_link:
+                        raise Exception('Google Drive cover url not found')
+
+                    stream = None
+                    try:
+                        stream = urlopen(web_content_link)
+                        with Image(file=stream) as img:
+                            # Use the first image in this set to determine the width and height to scale the
+                            # other images in this set
+                            if width == 0 or height == 0:
+                                width = get_resize_width(thumbnail.resolution, img.width, img.height)
+                                height = get_resize_height(thumbnail.resolution)
+                                canvas.blank(width, height)
+
+                            dimensions = get_best_fit(width, height, img.width, img.height)
+
+                            # resize and crop the image
+                            img.resize(width=int(dimensions['width']), height=int(dimensions['height']), filter='lanczos')
+                            img.crop(width=int(width / 2.0), height=int(height / 2.0), gravity='center')
+
+                            # add the image to the canvas
+                            canvas.composite(img, left, top)
+
+                    except Exception as ex:
+                        self.log.info(u'Error generating thumbnail file: ' + str(ex))
+                        raise ex
+                    finally:
+                        if stream is not None:
+                            stream.close()
+
+                book_cover_filepath = os.path.join(config.config_calibre_dir, book.path, 'cover.jpg')
+                if not os.path.isfile(book_cover_filepath):
+                    raise Exception('Book cover file not found')
+
+                with Image(filename=book_cover_filepath) as img:
+                    # Use the first image in this set to determine the width and height to scale the
+                    # other images in this set
+                    if width == 0 or height == 0:
+                        width = get_resize_width(thumbnail.resolution, img.width, img.height)
+                        height = get_resize_height(thumbnail.resolution)
+                        canvas.blank(width, height)
+
+                    dimensions = get_best_fit(width, height, img.width, img.height)
+
+                    # resize and crop the image
+                    img.resize(width=int(dimensions['width']), height=int(dimensions['height']), filter='lanczos')
+                    img.crop(width=int(width / 2.0), height=int(height / 2.0), gravity='center')
+
+                    # add the image to the canvas
+                    canvas.composite(img, left, top)
+
+                # set the coordinates for the next iteration
+                if left == 0 and top == 0:
+                    left = int(width / 2.0)
+                elif left == int(width / 2.0) and top == 0:
+                    left = 0
+                    top = int(height / 2.0)
+                else:
+                    left = int(width / 2.0)
+
+            canvas.format = thumbnail.format
+            filename = self.cache.get_cache_file_path(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
+            canvas.save(filename=filename)
+
+    @property
+    def name(self):
+        return 'GenerateSeriesThumbnails'
+
+    @property
+    def is_cancellable(self):
+        return True
+
+
+class TaskClearCoverThumbnailCache(CalibreTask):
+    def __init__(self, book_id, task_message=u'Clearing cover thumbnail cache'):
+        super(TaskClearCoverThumbnailCache, self).__init__(task_message)
+        self.log = logger.create()
+        self.book_id = book_id
+        self.app_db_session = ub.get_new_session_instance()
+        self.cache = fs.FileSystem()
+
+    def run(self, worker_thread):
+        if self.app_db_session:
+            if self.book_id:
+                thumbnails = self.get_thumbnails_for_book(self.book_id)
+                for thumbnail in thumbnails:
+                    self.delete_thumbnail(thumbnail)
+
+        self._handleSuccess()
+        self.app_db_session.remove()
+
+    def get_thumbnails_for_book(self, book_id):
+        return self.app_db_session \
+            .query(ub.Thumbnail) \
+            .filter(ub.Thumbnail.type == constants.THUMBNAIL_TYPE_COVER) \
+            .filter(ub.Thumbnail.entity_id == book_id) \
+            .all()
+
+    def delete_thumbnail(self, thumbnail):
+        thumbnail.expiration = datetime.utcnow()
+
+        try:
+            self.app_db_session.commit()
+            self.cache.delete_cache_file(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
+        except Exception as ex:
+            self.log.info(u'Error deleting book thumbnail: ' + str(ex))
+            self._handleError(u'Error deleting book thumbnail: ' + str(ex))
+
+    @property
+    def name(self):
+        return 'ThumbnailsClear'
+
+    @property
+    def is_cancellable(self):
+        return False
diff --git a/cps/tasks/upload.py b/cps/tasks/upload.py
index 2a667c2875..1c6c48c8f4 100644
--- a/cps/tasks/upload.py
+++ b/cps/tasks/upload.py
@@ -35,3 +35,7 @@ def name(self):
 
     def __str__(self):
         return "Upload {}".format(self.message)
+
+    @property
+    def is_cancellable(self):
+        return False
diff --git a/cps/templates/admin.html b/cps/templates/admin.html
index 6cd7815e00..8f749e0b66 100644
--- a/cps/templates/admin.html
+++ b/cps/templates/admin.html
@@ -160,15 +160,42 @@ <h2>{{_('Configuration')}}</h2>
     </div>
   </div>
 
-    <div class="row form-group">
-    <h2>{{_('Administration')}}</h2>
-      <a class="btn btn-default" id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a>
-      <a class="btn btn-default" id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logs')}}</a>
+  <div class="row">
+    <div class="col">
+      <h2>{{_('Scheduled Tasks')}}</h2>
+        <div class="col-xs-12 col-sm-12 scheduled_tasks_details">
+          <div class="row">
+            <div class="col-xs-6 col-sm-3">{{_('Time at which tasks start to run')}}</div>
+            <div class="col-xs-6 col-sm-3">{{config.schedule_start_time}}:00</div>
+          </div>
+          <div class="row">
+            <div class="col-xs-6 col-sm-3">{{_('Time at which tasks stop running')}}</div>
+            <div class="col-xs-6 col-sm-3">{{config.schedule_end_time}}:00</div>
+          </div>
+          <div class="row">
+            <div class="col-xs-6 col-sm-3">{{_('Generate book cover thumbnails')}}</div>
+            <div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.schedule_generate_book_covers) }}</div>
+          </div>
+          <div class="row">
+            <div class="col-xs-6 col-sm-3">{{_('Generate series cover thumbnails')}}</div>
+            <div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.schedule_generate_series_covers) }}</div>
+          </div>
+        </div>
+      <a class="btn btn-default scheduledtasks" id="admin_edit_scheduled_tasks" href="{{url_for('admin.edit_scheduledtasks')}}">{{_('Edit Scheduled Tasks Settings')}}</a>
     </div>
-    <div class="row form-group">
-      <div class="btn btn-default" id="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div>
-      <div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div>
-      <div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</div>
+  </div>
+
+  <div class="row form-group">
+    <h2>{{_('Administration')}}</h2>
+    <a class="btn btn-default" id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a>
+    <a class="btn btn-default" id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logs')}}</a>
+  </div>
+  <div class="row form-group">
+    <div class="btn btn-default" id="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div>
+  </div>
+  <div class="row form-group">
+    <div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div>
+    <div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</div>
   </div>
 
   <div class="row">
diff --git a/cps/templates/author.html b/cps/templates/author.html
index d832ad5c16..9f124876f2 100644
--- a/cps/templates/author.html
+++ b/cps/templates/author.html
@@ -37,7 +37,7 @@ <h3>{{_("In Library")}}</h3>
       <div class="cover">
         <a href="{{ url_for('web.show_book', book_id=entry.id) }}">
             <span class="img" title="{{entry.title}}">
-              <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
+              {{ image.book_cover(entry, alt=author.name|safe) }}
               {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
             </span>
         </a>
diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html
index 57470858c8..52b1e7fbdb 100644
--- a/cps/templates/book_edit.html
+++ b/cps/templates/book_edit.html
@@ -3,7 +3,8 @@
 {% if book %}
   <div class="col-sm-3 col-lg-3 col-xs-12">
     <div class="cover">
-        <img id="detailcover" title="{{book.title}}" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter)  }}" alt="{{ book.title }}"/>
+        <!-- Always use full-sized image for the book edit page -->
+        <img id="detailcover" title="{{book.title}}" src="{{url_for('web.get_cover', book_id=book.id, resolution='og', c=book|last_modified)}}" />
     </div>
 {% if g.user.role_delete_books() %}
     <div class="text-center">
diff --git a/cps/templates/detail.html b/cps/templates/detail.html
index 06357ce818..7e720e553b 100644
--- a/cps/templates/detail.html
+++ b/cps/templates/detail.html
@@ -4,7 +4,8 @@
   <div class="row">
     <div class="col-sm-3 col-lg-3 col-xs-5">
       <div class="cover">
-          <img id="detailcover" title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />
+        <!-- Always use full-sized image for the detail page -->
+        <img id="detailcover" title="{{entry.title}}" src="{{url_for('web.get_cover', book_id=entry.id, resolution='og', c=entry|last_modified)}}" />
       </div>
     </div>
     <div class="col-sm-9 col-lg-9 book-meta">
diff --git a/cps/templates/discover.html b/cps/templates/discover.html
index 74448b980e..4842a7ea9d 100644
--- a/cps/templates/discover.html
+++ b/cps/templates/discover.html
@@ -1,3 +1,4 @@
+{% import 'image.html' as image %}
 {% extends "layout.html" %}
 {% block body %}
 <div class="discover load-more">
@@ -9,7 +10,7 @@ <h2>{{title}}</h2>
         {% if entry.has_cover is defined %}
           <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
             <span class="img" title="{{entry.title}}">
-              <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
+              {{ image.book_cover(entry) }}
               {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
             </span>
           </a>
diff --git a/cps/templates/fragment.html b/cps/templates/fragment.html
index 1421ea6afd..f2e94fb2c3 100644
--- a/cps/templates/fragment.html
+++ b/cps/templates/fragment.html
@@ -1,3 +1,4 @@
+{% import 'image.html' as image %}
 <div class="container-fluid">
   {% block body %}{% endblock %}
 </div>
diff --git a/cps/templates/grid.html b/cps/templates/grid.html
index b9d409613d..638b724599 100644
--- a/cps/templates/grid.html
+++ b/cps/templates/grid.html
@@ -1,3 +1,4 @@
+{% import 'image.html' as image %}
 {% extends "layout.html" %}
 {% block body %}
 <h1 class="{{page}}">{{_(title)}}</h1>
@@ -27,7 +28,7 @@ <h1 class="{{page}}">{{_(title)}}</h1>
                   <div class="cover">
                       <a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
                           <span class="img" title="{{entry[0].series[0].name}}">
-                              <img src="{{ url_for('web.get_cover', book_id=entry[3]) }}" alt="{{ entry[0].series[0].name }}"/>
+                              {{ image.series(entry[0].series[0], alt=entry[0].series[0].name|shortentitle) }}
                               <span class="badge">{{entry.count}}</span>
                             </span>
                       </a>
diff --git a/cps/templates/image.html b/cps/templates/image.html
new file mode 100644
index 0000000000..0bdba9a51d
--- /dev/null
+++ b/cps/templates/image.html
@@ -0,0 +1,20 @@
+{% macro book_cover(book, alt=None) -%}
+    {%- set image_title = book.title if book.title else book.name -%}
+    {%- set image_alt = alt if alt else image_title -%}
+    {% set srcset = book|get_cover_srcset %}
+    <img
+        srcset="{{ srcset }}"
+        src="{{ url_for('web.get_cover', book_id=book.id, resolution='og', c=book|last_modified) }}"
+        alt="{{ image_alt }}"
+    />
+{%- endmacro %}
+
+{% macro series(series, alt=None) -%}
+    {%- set image_alt = alt if alt else image_title -%}
+    {% set srcset = series|get_series_srcset %}
+    <img
+        srcset="{{ srcset }}"
+        src="{{ url_for('web.get_series_cover', series_id=series.id, resolution='og', c='day'|cache_timestamp) }}"
+        alt="{{ book_title }}"
+    />
+{%- endmacro %}
diff --git a/cps/templates/index.html b/cps/templates/index.html
index 162adc7db9..ff7ff115fd 100644
--- a/cps/templates/index.html
+++ b/cps/templates/index.html
@@ -1,3 +1,4 @@
+{% import 'image.html' as image %}
 {% extends "layout.html" %}
 {% block body %}
 {% if g.user.show_detail_random() %}
@@ -9,7 +10,7 @@ <h2 class="random-books">{{_('Discover (Random Books)')}}</h2>
       <div class="cover">
           <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
               <span class="img" title="{{ entry.title }}">
-                <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
+                {{ image.book_cover(entry) }}
                 {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
               </span>
           </a>
@@ -91,7 +92,7 @@ <h2 class="{{title}}">{{title}}</h2>
       <div class="cover">
           <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
             <span class="img" title="{{ entry.title }}">
-              <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
+              {{ image.book_cover(entry) }}
               {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
             </span>
           </a>
diff --git a/cps/templates/layout.html b/cps/templates/layout.html
index ec69c91ba6..c60f63d412 100644
--- a/cps/templates/layout.html
+++ b/cps/templates/layout.html
@@ -1,4 +1,5 @@
 {% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %}
+{% import 'image.html' as image %}
 <!DOCTYPE html>
 <html lang="{{ g.user.locale }}">
   <head>
diff --git a/cps/templates/schedule_edit.html b/cps/templates/schedule_edit.html
new file mode 100644
index 0000000000..f989baf9fa
--- /dev/null
+++ b/cps/templates/schedule_edit.html
@@ -0,0 +1,39 @@
+{% extends "layout.html" %}
+{% block header %}
+<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
+<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
+{% endblock %}
+{% block body %}
+<div class="discover">
+  <h1>{{title}}</h1>
+  <form role="form" class="col-md-10 col-lg-6" method="POST" autocomplete="off">
+    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
+    <div class="form-group">
+      <label for="schedule_start_time">{{_('Time at which tasks start to run')}}</label>
+      <select name="schedule_start_time" id="schedule_start_time" class="form-control">
+        {% for n in range(24) %}
+          <option value="{{n}}" {% if config.schedule_start_time == n %}selected{% endif %}>{{n}}{{_(':00')}}</option>
+        {% endfor %}
+      </select>
+    </div>
+    <div class="form-group">
+      <label for="schedule_end_time">{{_('Time at which tasks stop running')}}</label>
+      <select name="schedule_end_time" id="schedule_end_time" class="form-control">
+        {% for n in range(24) %}
+          <option value="{{n}}" {% if config.schedule_end_time == n %}selected{% endif %}>{{n}}{{_(':00')}}</option>
+        {% endfor %}
+      </select>
+    </div>
+    <div class="form-group">
+      <input type="checkbox" id="schedule_generate_book_covers" name="schedule_generate_book_covers" {% if config.schedule_generate_book_covers %}checked{% endif %}>
+      <label for="schedule_generate_book_covers">{{_('Generate Book Cover Thumbnails')}}</label>
+    </div>
+    <div class="form-group">
+      <input type="checkbox" id="schedule_generate_series_covers" name="schedule_generate_series_covers" {% if config.schedule_generate_series_covers %}checked{% endif %}>
+      <label for="schedule_generate_series_covers">{{_('Generate Series Cover Thumbnails')}}</label>
+    </div>
+    <button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
+    <a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Cancel')}}</a>
+  </form>
+</div>
+{% endblock %}
diff --git a/cps/templates/search.html b/cps/templates/search.html
index a7ba5e68ad..129663487f 100644
--- a/cps/templates/search.html
+++ b/cps/templates/search.html
@@ -1,3 +1,4 @@
+{% import 'image.html' as image %}
 {% extends "layout.html" %}
 {% block body %}
 <div class="discover">
@@ -45,7 +46,7 @@ <h2>{{result_count}} {{_('Results for:')}} {{adv_searchterm}}</h2>
         {% if entry.Books.has_cover is defined %}
            <a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
             <span class="img" title="{{entry.Books.title}}" >
-                <img src="{{ url_for('web.get_cover', book_id=entry.Books.id) }}" alt="{{ entry.Books.title }}" />
+                {{ image.book_cover(entry.Books) }}
                 {% if entry.Books.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
             </span>
           </a>
diff --git a/cps/templates/shelf.html b/cps/templates/shelf.html
index adfead60bd..693f6b4ac6 100644
--- a/cps/templates/shelf.html
+++ b/cps/templates/shelf.html
@@ -1,3 +1,4 @@
+{% import 'image.html' as image %}
 {% extends "layout.html" %}
 {% block body %}
 <div class="discover">
@@ -34,7 +35,7 @@ <h2>{{title}}</h2>
       <div class="cover">
             <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
               <span class="img" title="{{entry.title}}" >
-                <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
+                {{ image.book_cover(entry) }}
                 {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
               </span>
             </a>
diff --git a/cps/templates/tasks.html b/cps/templates/tasks.html
index 3ef5047487..0e5a352bd1 100644
--- a/cps/templates/tasks.html
+++ b/cps/templates/tasks.html
@@ -16,6 +16,9 @@ <h2>{{_('Tasks')}}</h2>
             <th data-halign="right" data-align="right" data-field="progress" data-sortable="true" data-sorter="elementSorter">{{_('Progress')}}</th>
             <th data-halign="right" data-align="right" data-field="runtime" data-sortable="true" data-sort-name="rt">{{_('Run Time')}}</th>
             <th data-halign="right" data-align="right" data-field="starttime" data-sortable="true" data-sort-name="id">{{_('Start Time')}}</th>
+            {% if g.user.role_admin() %}
+            <th data-halign="right" data-align="right" data-formatter="TaskActions" data-switchable="false">{{_('Actions')}}</th>
+            {% endif %}
             <th data-field="id" data-visible="false"></th>
             <th data-field="rt" data-visible="false"></th>
         </tr>
@@ -23,6 +26,30 @@ <h2>{{_('Tasks')}}</h2>
     </table>
 </div>
 {% endblock %}
+{% block modal %}
+{{ delete_book() }}
+{% if g.user.role_admin() %}
+<div class="modal fade" id="cancelTaskModal" role="dialog" aria-labelledby="metaCancelTaskLabel">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-header bg-danger text-center">
+          <span>{{_('Are you really sure?')}}</span>
+      </div>
+        <div class="modal-body text-center">
+          <p>
+            <span>{{_('This task will be cancelled. Any progress made by this task will be saved.')}}</span>
+            <span>{{_('If this is a scheduled task, it will be re-ran during the next scheduled time.')}}</span>
+          </p>
+        </div>
+      <div class="modal-footer">
+        <input type="button" class="btn btn-danger" value="{{_('Ok')}}" name="cancel_task_confirm" id="cancel_task_confirm" data-dismiss="modal">
+        <button type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
+      </div>
+    </div>
+  </div>
+</div>
+{% endif %}
+{% endblock %}
 {% block js %}
 <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
 <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-locale-all.min.js') }}"></script>
diff --git a/cps/ub.py b/cps/ub.py
index e52ce2013d..9d8a8b4b11 100644
--- a/cps/ub.py
+++ b/cps/ub.py
@@ -17,6 +17,7 @@
 #  You should have received a copy of the GNU General Public License
 #  along with this program. If not, see <http://www.gnu.org/licenses/>.
 
+import atexit
 import os
 import sys
 import datetime
@@ -509,6 +510,28 @@ def __repr__(self):
         return '<Token %r>' % self.id
 
 
+def filename(context):
+    file_format = context.get_current_parameters()['format']
+    if file_format == 'jpeg':
+        return context.get_current_parameters()['uuid'] + '.jpg'
+    else:
+        return context.get_current_parameters()['uuid'] + '.' + file_format
+
+
+class Thumbnail(Base):
+    __tablename__ = 'thumbnail'
+
+    id = Column(Integer, primary_key=True)
+    entity_id = Column(Integer)
+    uuid = Column(String, default=lambda: str(uuid.uuid4()), unique=True)
+    format = Column(String, default='jpeg')
+    type = Column(SmallInteger, default=constants.THUMBNAIL_TYPE_COVER)
+    resolution = Column(SmallInteger, default=constants.COVER_THUMBNAIL_SMALL)
+    filename = Column(String, default=filename)
+    generated_at = Column(DateTime, default=lambda: datetime.datetime.utcnow())
+    expiration = Column(DateTime, nullable=True)
+
+
 # Add missing tables during migration of database
 def add_missing_tables(engine, session):
     if not engine.dialect.has_table(engine.connect(), "book_read_link"):
@@ -523,6 +546,8 @@ def add_missing_tables(engine, session):
         KoboStatistics.__table__.create(bind=engine)
     if not engine.dialect.has_table(engine.connect(), "archived_book"):
         ArchivedBook.__table__.create(bind=engine)
+    if not engine.dialect.has_table(engine.connect(), "thumbnail"):
+        Thumbnail.__table__.create(bind=engine)
     if not engine.dialect.has_table(engine.connect(), "registration"):
         Registration.__table__.create(bind=engine)
         with engine.connect() as conn:
@@ -828,6 +853,16 @@ def init_db(app_db_path):
             sys.exit(3)
 
 
+def get_new_session_instance():
+    new_engine = create_engine(u'sqlite:///{0}'.format(cli.settingspath), echo=False)
+    new_session = scoped_session(sessionmaker())
+    new_session.configure(bind=new_engine)
+
+    atexit.register(lambda: new_session.remove() if new_session else True)
+
+    return new_session
+
+
 def dispose():
     global session
 
diff --git a/cps/updater.py b/cps/updater.py
index 9090263fbf..2ef246809d 100644
--- a/cps/updater.py
+++ b/cps/updater.py
@@ -264,7 +264,7 @@ def update_source(self, source, destination):
             os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv',
             os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2',
             os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR',
-            os.sep + 'gmail.json'
+            os.sep + 'gmail.json', os.sep + 'cps' + os.sep + 'cache'
         )
         additional_path = self.is_venv()
         if additional_path:
diff --git a/cps/web.py b/cps/web.py
index 7ecb7e0e6a..fb736ee096 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -48,8 +48,8 @@
 from . import babel, db, ub, config, get_locale, app
 from . import calibre_db, kobo_sync_status
 from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
-from .helper import check_valid_domain, render_task_status, check_email, check_username, \
-    get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
+from .helper import check_valid_domain, render_task_status, check_email, check_username, get_cc_columns, \
+    get_book_cover, get_series_cover_thumbnail, get_download_link, send_mail, generate_random_password, \
     send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password, valid_email
 from .pagination import Pagination
 from .redirect import redirect_back
@@ -127,7 +127,7 @@ def inner(*args, **kwargs):
 @web.route("/ajax/emailstat")
 @login_required
 def get_email_status_json():
-    tasks = WorkerThread.getInstance().tasks
+    tasks = WorkerThread.get_instance().tasks
     return jsonify(render_task_status(tasks))
 
 
@@ -794,6 +794,7 @@ def books_table():
     return render_title_template('book_table.html', title=_(u"Books List"), cc=cc, page="book_table",
                                  visiblility=visibility)
 
+
 @web.route("/ajax/listbooks")
 @login_required
 def list_books():
@@ -891,6 +892,7 @@ def list_books():
     response.headers["Content-Type"] = "application/json; charset=utf-8"
     return response
 
+
 @web.route("/ajax/table_settings", methods=['POST'])
 @login_required
 def update_table_settings():
@@ -970,6 +972,7 @@ def publisher_list():
         charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
             .join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
             .group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
+
         return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
                                      title=_(u"Publishers"), page="publisherlist", data="publisher", order=order_no)
     else:
@@ -1099,7 +1102,7 @@ def category_list():
 @login_required
 def get_tasks_status():
     # if current user admin, show all email, otherwise only own emails
-    tasks = WorkerThread.getInstance().tasks
+    tasks = WorkerThread.get_instance().tasks
     answer = render_task_status(tasks)
     return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
 
@@ -1435,7 +1438,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
                                  pagination=pagination,
                                  entries=entries,
                                  result_count=result_count,
-                                 title=_(u"Advanced Search"), page="advsearch",
+                                 title=_(u"Advanced Search"),
+                                 page="advsearch",
                                  order=order[1])
 
 
@@ -1451,14 +1455,38 @@ def advanced_search_form():
 
 
 @web.route("/cover/<int:book_id>")
+@web.route("/cover/<int:book_id>/<string:resolution>")
 @login_required_if_no_ano
-def get_cover(book_id):
-    return get_book_cover(book_id)
+def get_cover(book_id, resolution=None):
+    resolutions = {
+        'og': constants.COVER_THUMBNAIL_ORIGINAL,
+        'sm': constants.COVER_THUMBNAIL_SMALL,
+        'md': constants.COVER_THUMBNAIL_MEDIUM,
+        'lg': constants.COVER_THUMBNAIL_LARGE,
+    }
+    cover_resolution = resolutions.get(resolution, None)
+    return get_book_cover(book_id, cover_resolution)
+
+
+@web.route("/series_cover/<int:series_id>")
+@web.route("/series_cover/<int:series_id>/<string:resolution>")
+@login_required_if_no_ano
+def get_series_cover(series_id, resolution=None):
+    resolutions = {
+        'og': constants.COVER_THUMBNAIL_ORIGINAL,
+        'sm': constants.COVER_THUMBNAIL_SMALL,
+        'md': constants.COVER_THUMBNAIL_MEDIUM,
+        'lg': constants.COVER_THUMBNAIL_LARGE,
+    }
+    cover_resolution = resolutions.get(resolution, None)
+    return get_series_cover_thumbnail(series_id, cover_resolution)
+
 
 @web.route("/robots.txt")
 def get_robots():
     return send_from_directory(constants.STATIC_DIR, "robots.txt")
 
+
 @web.route("/show/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
 @web.route("/show/<int:book_id>/<book_format>/<anyname>")
 @login_required_if_no_ano
diff --git a/requirements.txt b/requirements.txt
index 0b95dfa37c..ef79d5518f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+APScheduler>=3.6.3,<3.8.0
 Babel>=1.3,<3.0
 Flask-Babel>=0.11.1,<2.1.0
 Flask-Login>=0.3.2,<0.5.1


Test patch:
diff --git a/test/Calibre-Web TestSummary_Windows.html b/test/Calibre-Web TestSummary_Windows.html
index 6668632d08..21a416127f 100644
--- a/test/Calibre-Web TestSummary_Windows.html
+++ b/test/Calibre-Web TestSummary_Windows.html
@@ -37,20 +37,20 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
       <div class="row">
         <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
             
-            <p class='text-justify attribute'><strong>Start Time: </strong>2021-05-18 17:33:17</p>
+            <p class='text-justify attribute'><strong>Start Time: </strong>2021-10-06 00:29:44</p>
             
         </div>
       </div>
       <div class="row">
         <div class="col-xs-6 col-md-6 col-sm-offset-3">
             
-            <p class='text-justify attribute'><strong>Stop Time: </strong>2021-05-18 20:37:38</p>
+            <p class='text-justify attribute'><strong>Stop Time: </strong>2021-10-06 00:37:52</p>
             
         </div>
       </div>
       <div class="row">
         <div class="col-xs-6 col-md-6 col-sm-offset-3">
-           <p class='text-justify attribute'><strong>Duration: </strong>2h 35 min</p>
+           <p class='text-justify attribute'><strong>Duration: </strong>7:28 min</p>
         </div>
       </div>
       </div>
@@ -102,337 +102,49 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     </tr>
 
 
-    <tr id="su" class="passClass">
-        <td>TestAnonymous</td>
-        <td class="text-center">13</td>
-        <td class="text-center">13</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c1', 13)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt1.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_check_locale_guest</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_about</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_category</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_format</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_hot</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_rated</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_rating</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_change_visibility_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_random_books_available</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_restricted_settings_visibility</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt1.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestAnonymous - test_guest_visibility_sidebar</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestCli</td>
-        <td class="text-center">8</td>
-        <td class="text-center">8</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c2', 8)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt2.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_already_started</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_bind_to_single_interface</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_change_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_cli_SSL_files</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_cli_different_folder</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_cli_different_settings_database</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_environ_port_setting</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt2.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCli - test_settingsdb_not_writeable</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestCliGdrivedb</td>
-        <td class="text-center">2</td>
-        <td class="text-center">2</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c3', 2)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt3.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCliGdrivedb - test_cli_gdrive_location</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt3.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCliGdrivedb - test_gdrive_db_nonwrite</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestCoverEditBooks</td>
-        <td class="text-center">1</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c4', 1)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt4.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCoverEditBooks - test_upload_jpg</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestDeleteDatabase</td>
+    <tr id="su" class="errorClass">
+        <td>TestShelf</td>
+        <td class="text-center">24</td>
         <td class="text-center">1</td>
+        <td class="text-center">9</td>
+        <td class="text-center">13</td>
         <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
         <td class="text-center">
-            <a onclick="showClassDetail('c5', 1)">Detail</a>
+            <a onclick="showClassDetail('c1', 24)">Detail</a>
         </td>
     </tr>
 
     
     
-        <tr id='pt5.1' class='hiddenRow bg-success'>
+        <tr id='pt1.1' class='hiddenRow bg-success'>
             <td>
-                <div class='testcase'>TestDeleteDatabase - test_delete_books_in_database</div>
+                <div class='testcase'>TestShelf - test_add_shelf_from_search</div>
             </td>
             <td colspan='6' align='center'>PASS</td>
         </tr>
     
     
-
-
-    <tr id="su" class="skipClass">
-        <td>TestEbookConvertCalibre</td>
-        <td class="text-center">11</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">11</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c6', 11)">Detail</a>
-        </td>
-    </tr>
-
-    
     
-        <tr id="st6.1" class="none bg-warning">
+        <tr id="et1.2" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_convert_deactivate</div>
+                <div class='testcase'>TestShelf - test_adv_search_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.1')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.2')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.1" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.2" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.1').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.2').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 428, in test_adv_search_shelf
+    self.check_element_on_page((By.ID, "add-to-shelf")).click()
+AttributeError: 'bool' object has no attribute 'click'</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -442,23 +154,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.2" class="none bg-warning">
+        <tr id="et1.3" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_convert_email</div>
+                <div class='testcase'>TestShelf - test_adv_search_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.2')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.3')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.2" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.3" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.2').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.3').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -468,23 +187,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.3" class="none bg-warning">
+        <tr id="ft1.4" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_convert_failed_and_email</div>
+                <div class='testcase'>TestShelf - test_arrange_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.3')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.4')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.3" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.4" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.3').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.4').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 226, in test_arrange_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -494,23 +216,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.4" class="none bg-warning">
+        <tr id="et1.5" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_convert_only</div>
+                <div class='testcase'>TestShelf - test_arrange_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.4')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.5')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.4" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.5" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.4').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.5').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -520,23 +249,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.5" class="none bg-warning">
+        <tr id="ft1.6" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_convert_parameter</div>
+                <div class='testcase'>TestShelf - test_delete_book_of_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.5')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.6')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.5" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.6" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.5').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.6').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 211, in test_delete_book_of_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -546,23 +278,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.6" class="none bg-warning">
+        <tr id="et1.7" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_convert_wrong_excecutable</div>
+                <div class='testcase'>TestShelf - test_delete_book_of_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.6')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.7')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.6" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.7" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.6').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.7').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -572,23 +311,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.7" class="none bg-warning">
+        <tr id="ft1.8" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_email_failed</div>
+                <div class='testcase'>TestShelf - test_private_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.7')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.8')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.7" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.8" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.7').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.8').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 55, in test_private_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -598,23 +340,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.8" class="none bg-warning">
+        <tr id="et1.9" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_email_only</div>
+                <div class='testcase'>TestShelf - test_private_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.8')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.9')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.8" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.9" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.8').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.9').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -624,23 +373,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.9" class="none bg-warning">
+        <tr id="ft1.10" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_kindle_send_not_configured</div>
+                <div class='testcase'>TestShelf - test_public_private_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.9')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.10')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.9" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.10" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.9').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.10').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 265, in test_public_private_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -650,23 +402,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.10" class="none bg-warning">
+        <tr id="et1.11" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_ssl_smtp_setup_error</div>
+                <div class='testcase'>TestShelf - test_public_private_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.10')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.11')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.10" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.11" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.10').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.11').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -676,23 +435,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st6.11" class="none bg-warning">
+        <tr id="ft1.12" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibre - test_starttls_smtp_setup_error</div>
+                <div class='testcase'>TestShelf - test_public_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st6.11')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.12')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st6.11" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.12" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st6.11').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.12').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 93, in test_public_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -701,91 +463,31 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
         </tr>
     
     
-
-
-    <tr id="su" class="skipClass">
-        <td>TestEbookConvertCalibreGDrive</td>
-        <td class="text-center">6</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">6</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c7', 6)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="st7.1" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestEbookConvertCalibreGDrive - test_convert_email</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st7.1')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st7.1" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st7.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st7.2" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestEbookConvertCalibreGDrive - test_convert_failed_and_email</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st7.2')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st7.2" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st7.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
     
-    
-        <tr id="st7.3" class="none bg-warning">
+        <tr id="et1.13" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibreGDrive - test_convert_only</div>
+                <div class='testcase'>TestShelf - test_public_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st7.3')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.13')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st7.3" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.13" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st7.3').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.13').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -795,23 +497,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st7.4" class="none bg-warning">
+        <tr id="ft1.14" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibreGDrive - test_convert_parameter</div>
+                <div class='testcase'>TestShelf - test_rename_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st7.4')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.14')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st7.4" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.14" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st7.4').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.14').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 146, in test_rename_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -821,23 +526,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st7.5" class="none bg-warning">
+        <tr id="et1.15" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertCalibreGDrive - test_email_failed</div>
+                <div class='testcase'>TestShelf - test_rename_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st7.5')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.15')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st7.5" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.15" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st7.5').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.15').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -847,23 +559,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st7.6" class="none bg-warning">
+        <tr id="ft1.16" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertCalibreGDrive - test_email_only</div>
+                <div class='testcase'>TestShelf - test_shelf_action_non_shelf_edit_role</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st7.6')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.16')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st7.6" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.16" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st7.6').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.16').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 304, in test_shelf_action_non_shelf_edit_role
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -872,39 +587,31 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
         </tr>
     
     
-
-
-    <tr id="su" class="skipClass">
-        <td>TestEbookConvertKepubify</td>
-        <td class="text-center">3</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">3</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c8', 3)">Detail</a>
-        </td>
-    </tr>
-
-    
     
-        <tr id="st8.1" class="none bg-warning">
+        <tr id="et1.17" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertKepubify - test_convert_deactivate</div>
+                <div class='testcase'>TestShelf - test_shelf_action_non_shelf_edit_role</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st8.1')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.17')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st8.1" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.17" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st8.1').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.17').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, kepubify not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -914,23 +621,32 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st8.2" class="none bg-warning">
+        <tr id="et1.18" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertKepubify - test_convert_only</div>
+                <div class='testcase'>TestShelf - test_shelf_anonymous</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st8.2')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.18')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st8.2" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.18" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st8.2').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.18').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, kepubify not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 361, in test_shelf_anonymous
+    self.fill_basic_config({'config_anonbrowse': 1})
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 341, in fill_basic_config
+    cls._fill_basic_config(elements)
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 252, in _fill_basic_config
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "config_port")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -940,23 +656,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st8.3" class="none bg-warning">
+        <tr id="et1.19" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertKepubify - test_convert_wrong_excecutable</div>
+                <div class='testcase'>TestShelf - test_shelf_anonymous</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st8.3')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.19')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st8.3" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.19" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st8.3').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.19').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, kepubify not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -965,39 +688,24 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
         </tr>
     
     
-
-
-    <tr id="su" class="skipClass">
-        <td>TestEbookConvertGDriveKepubify</td>
-        <td class="text-center">3</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">3</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c9', 3)">Detail</a>
-        </td>
-    </tr>
-
-    
     
-        <tr id="st9.1" class="none bg-warning">
+        <tr id="st1.20" class="none bg-warning">
             <td>
-                <div class='testcase'>TestEbookConvertGDriveKepubify - test_convert_deactivate</div>
+                <div class='testcase'>TestShelf - test_shelf_database_change</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st9.1')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st1.20')">SKIP</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st9.1" class="popup_window test_output" style="display:none;">
+                <div id="div_st1.20" class="popup_window test_output" style="display:none;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st9.1').style.display='none'"><span
+                                onclick='document.getElementById('div_st1.20').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, kepubify not found</pre>
+                        <pre class="text-left">Change Database Not Implemented</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -1007,23 +715,26 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st9.2" class="none bg-warning">
+        <tr id="ft1.21" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEbookConvertGDriveKepubify - test_convert_only</div>
+                <div class='testcase'>TestShelf - test_shelf_long_name</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st9.2')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.21')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st9.2" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.21" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st9.2').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.21').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, kepubify not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 298, in test_shelf_long_name
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -1033,23 +744,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st9.3" class="none bg-warning">
+        <tr id="et1.22" class="none bg-info">
             <td>
-                <div class='testcase'>TestEbookConvertGDriveKepubify - test_convert_wrong_excecutable</div>
+                <div class='testcase'>TestShelf - test_shelf_long_name</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st9.3')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.22')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st9.3" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.22" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st9.3').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.22').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, kepubify not found</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -1058,138 +776,27 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
         </tr>
     
     
-
-
-    <tr id="su" class="skipClass">
-        <td>TestEditAdditionalBooks</td>
-        <td class="text-center">13</td>
-        <td class="text-center">11</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">2</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c10', 13)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt10.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_change_upload_formats</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_delete_book</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_delete_role</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_edit_book_identifier</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_edit_book_identifier_capital</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_edit_book_identifier_standard</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_edit_special_book_identifier</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_title_sort</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_upload_edit_role</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_upload_metadata_cbr</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt10.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_upload_metadata_cbt</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
     
-        <tr id="st10.12" class="none bg-warning">
+        <tr id="ft1.23" class="none bg-danger">
             <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_writeonly_calibre_database</div>
+                <div class='testcase'>TestShelf - test_xss_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st10.12')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft1.23')">FAIL</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st10.12" class="popup_window test_output" style="display:none;">
+                <div id="div_ft1.23" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st10.12').style.display='none'"><span
+                                onclick='document.getElementById('div_ft1.23').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">Not implemented</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 443, in test_xss_shelf
+    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
+AssertionError: False is not true</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -1199,23 +806,30 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
     
-        <tr id="st10.13" class="none bg-warning">
+        <tr id="et1.24" class="none bg-info">
             <td>
-                <div class='testcase'>TestEditAdditionalBooks - test_writeonly_path</div>
+                <div class='testcase'>TestShelf - test_xss_shelf</div>
             </td>
             <td colspan='6'>
                 <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st10.13')">SKIP</a>
+                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et1.24')">ERROR</a>
                 </div>
                 <!--css div popup start-->
-                <div id="div_st10.13" class="popup_window test_output" style="display:none;">
+                <div id="div_et1.24" class="popup_window test_output" style="display:block;">
                     <div class='close_button pull-right'>
                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st10.13').style.display='none'"><span
+                                onclick='document.getElementById('div_et1.24').style.display='none'"><span
                                 aria-hidden="true">&times;</span></button>
                     </div>
                     <div class="text-left pull-left">
-                        <pre class="text-left">writeonly database on windows is not checked</pre>
+                        <pre class="text-left">Traceback (most recent call last):
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\test_shelf.py", line 42, in tearDown
+    self.login('admin','admin123')
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\test\helper_ui.py", line 71, in login
+    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "username")))
+  File "C:\Users\cj.ohara\Repositories\calibre-web-test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
+    raise TimeoutException(message, screen, stacktrace)
+selenium.common.exceptions.TimeoutException: Message:</pre>
                     </div>
                     <div class="clearfix"></div>
                 </div>
@@ -1225,4185 +839,14 @@ <h1 id='report_title' class="text-center">Calibre-Web Tests</h1>
     
     
 
-
-    <tr id="su" class="failClass">
-        <td>TestEditBooks</td>
-        <td class="text-center">35</td>
-        <td class="text-center">32</td>
-        <td class="text-center">2</td>
-        <td class="text-center">0</td>
-        <td class="text-center">1</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c11', 35)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt11.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_download_book</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_author</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_category</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_comments</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_bool</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_categories</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_comment</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_date</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_float</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_int</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_rating</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_single_select</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_custom_text</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.16' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_publishing_date</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.17' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_rating</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.18' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.19' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_edit_title</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="st11.20" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestEditBooks - test_rename_uppercase_lowercase</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st11.20')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st11.20" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st11.20').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Not Implemented</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.21' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_typeahead_author</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.22' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_typeahead_functions</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.23' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_typeahead_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.24' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_typeahead_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.25' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_typeahead_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.26' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_typeahead_tag</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.27' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_cbr</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.28' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_cbt</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.29' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_cbz</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.30' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_epub</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft11.31" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_fb2</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft11.31')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft11.31" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft11.31').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_edit_books.py", line 828, in test_upload_book_fb2
-    self.assertEqual('book55 - Lul执 de Marco', details['title'])
-AssertionError: 'book55 - Lul执 de Marco' != 'book55 - Lulæ‰§ de Marco'
-- book55 - Lul执 de Marco
-?             ^
-+ book55 - Lulæ‰§ de Marco
-?             ^^^</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.32' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_lit</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.33' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_mobi</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft11.34" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_book_pdf</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft11.34')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft11.34" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft11.34').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_edit_books.py", line 811, in test_upload_book_pdf
-    self.assertLess('23300', resp.headers['Content-Length'])
-AssertionError: '23300' not less than '19501'</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt11.35' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooks - test_upload_cover_hdd</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestEditBooksList</td>
-        <td class="text-center">10</td>
-        <td class="text-center">10</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c12', 10)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt12.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_author</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_categories</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_languages</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_seriesindex</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_bookslist_edit_title</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_list_visibility</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_restricted_rights</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt12.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksList - test_search_books_list</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="errorClass">
-        <td>TestEditBooksOnGdrive</td>
-        <td class="text-center">20</td>
-        <td class="text-center">16</td>
-        <td class="text-center">3</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c13', 20)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt13.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_download_book</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft13.2" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_author</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft13.2')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft13.2" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft13.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_edit_ebooks_gdrive.py", line 371, in test_edit_author
-    self.assertEqual(u'Pipo, Pipe', author.get_attribute('value'))
-AssertionError: 'Pipo, Pipe' != 'Pipo| Pipe'
-- Pipo, Pipe
-?     ^
-+ Pipo| Pipe
-?     ^</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_category</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_comments</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_bool</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_categories</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_float</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_int</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_rating</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_single_select</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_custom_text</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_rating</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="et13.16" class="none bg-info">
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_edit_title</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et13.16')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et13.16" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et13.16').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_edit_ebooks_gdrive.py", line 240, in test_edit_title
-    self.assertEqual(ele.text, u'Very long extra super turbo cool title without any issue of displaying including ö utf-8 characters')
-AttributeError: 'bool' object has no attribute 'text'</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.17' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_upload_book_epub</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt13.18' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_upload_book_lit</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft13.19" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_upload_cover_hdd</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft13.19')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft13.19" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft13.19').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_edit_ebooks_gdrive.py", line 773, in test_upload_cover_hdd
-    self.assertGreater(diff('bmp.png', 'jpeg.png', delete_diff_file=True), 0.006)
-AssertionError: 0.004266211170916505 not greater than 0.006</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="ft13.20" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestEditBooksOnGdrive - test_watch_metadata</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft13.20')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft13.20" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft13.20').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_edit_ebooks_gdrive.py", line 908, in test_watch_metadata
-    self.assertNotIn('series', book)
-AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': 'testbook', 'author': ['John Döe'], 'rating': 0, 'languages': ['English'], 'identifier': [], 'cover': '/cover/5?edit=2ac4dcbe-31a1-4812-b439-00968a0c3ab5', 'tag': [], 'publisher': ['Randomhäus'], 'pubdate': 'Jan 19, 2017', 'comment': 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Aenean commodo ligula eget dolor.Aenean massa.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.Nulla consequat massa quis enim.Donec pede justo, fringilla vel, aliquet nec, vulputate', 'add_shelf': [], 'del_shelf': [], 'edit_enable': True, 'kindle': None, 'kindlebtn': None, 'download': ['EPUB (6.7 kB)'], 'read': False, 'archived': False, 'series_all': 'Book 1.0 of test', 'series_index': '1.0', 'series': 'test', 'cust_columns': []}</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="skipClass">
-        <td>TestSTARTTLS</td>
-        <td class="text-center">3</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">3</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c14', 3)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="st14.1" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSTARTTLS - test_STARTTLS</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st14.1')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st14.1" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st14.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st14.2" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSTARTTLS - test_STARTTLS_SSL_setup_error</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st14.2')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st14.2" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st14.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st14.3" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSTARTTLS - test_STARTTLS_resend_password</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st14.3')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st14.3" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st14.3').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="skipClass">
-        <td>TestSSL</td>
-        <td class="text-center">6</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">6</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c15', 6)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="st15.1" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSSL - test_SSL_None_setup_error</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st15.1')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st15.1" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st15.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st15.2" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSSL - test_SSL_STARTTLS_setup_error</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st15.2')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st15.2" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st15.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st15.3" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSSL - test_SSL_logging_email</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st15.3')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st15.3" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st15.3').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st15.4" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSSL - test_SSL_non_admin_user</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st15.4')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st15.4" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st15.4').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st15.5" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSSL - test_SSL_only</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st15.5')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st15.5" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st15.5').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st15.6" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestSSL - test_email_limit</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st15.6')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st15.6" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st15.6').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Skipping convert, calibre not found</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestErrorReadColumn</td>
-        <td class="text-center">1</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c16', 1)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt16.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestErrorReadColumn - test_invalid_custom_column</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="skipClass">
-        <td>TestFilePicker</td>
-        <td class="text-center">3</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">2</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c17', 3)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="st17.1" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestFilePicker - test_filepicker_all_file</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st17.1')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st17.1" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st17.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Not implemented</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt17.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestFilePicker - test_filepicker_limited_file</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="st17.3" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestFilePicker - test_filepicker_new_file</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st17.3')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st17.3" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st17.3').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Not implemented</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestSetupGdrive</td>
-        <td class="text-center">1</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c18', 1)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt18.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestSetupGdrive - test_config_gdrive</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestGoodreads</td>
-        <td class="text-center">3</td>
-        <td class="text-center">3</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c19', 3)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt19.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestGoodreads - test_author_page</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt19.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestGoodreads - test_author_page_invalid</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt19.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestGoodreads - test_goodreads_about</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="errorClass">
-        <td>TestKoboSync</td>
-        <td class="text-center">10</td>
-        <td class="text-center">0</td>
-        <td class="text-center">1</td>
-        <td class="text-center">9</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c20', 10)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="et20.1" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_book_download</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.1')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.1" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 426, in _make_request
-    six.raise_from(e, None)
-  File "<string>", line 3, in raise_from
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 421, in _make_request
-    httplib_response = conn.getresponse()
-  File "C:\Python39\lib\http\client.py", line 1345, in getresponse
-    response.begin()
-  File "C:\Python39\lib\http\client.py", line 307, in begin
-    version, status, reason = self._read_status()
-  File "C:\Python39\lib\http\client.py", line 268, in _read_status
-    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
-  File "C:\Python39\lib\socket.py", line 704, in readinto
-    return self._sock.recv_into(b)
-socket.timeout: timed out
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\adapters.py", line 439, in send
-    resp = conn.urlopen(
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 726, in urlopen
-    retries = retries.increment(
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\util\retry.py", line 410, in increment
-    raise six.reraise(type(error), error, _stacktrace)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\packages\six.py", line 735, in reraise
-    raise value
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 670, in urlopen
-    httplib_response = self._make_request(
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 428, in _make_request
-    self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 335, in _raise_timeout
-    raise ReadTimeoutError(
-urllib3.exceptions.ReadTimeoutError: HTTPConnectionPool(host='192.168.188.57', port=8083): Read timed out. (read timeout=10)
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 579, in test_book_download
-    data = self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 118, in inital_sync
-    r = session.get(self.kobo_adress+'/v1/library/sync', params=params, headers=TestKoboSync.syncToken, timeout=10)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\sessions.py", line 543, in get
-    return self.request('GET', url, **kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\sessions.py", line 530, in request
-    resp = self.send(prep, **send_kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\sessions.py", line 643, in send
-    r = adapter.send(request, **kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\adapters.py", line 529, in send
-    raise ReadTimeout(e, request=request)
-requests.exceptions.ReadTimeout: HTTPConnectionPool(host='192.168.188.57', port=8083): Read timed out. (read timeout=10)</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="ft20.2" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestKoboSync - test_kobo_about</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft20.2')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft20.2" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft20.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 576, in test_kobo_about
-    self.assertTrue(self.goto_page('nav_about'))
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.3" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_kobo_sync_selected_shelfs</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.3')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.3" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.3').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 598, in test_kobo_sync_selected_shelfs
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.WebDriverException: Message: TypeError: this.curBrowser.contentBrowser is null</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.4" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_shelves_add_remove_books</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.4')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.4" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.4').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 443, in test_shelves_add_remove_books
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.5" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_sync_changed_book</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.5')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.5" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.5').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 273, in test_sync_changed_book
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.6" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_sync_invalid</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.6')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.6" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.6').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 426, in _make_request
-    six.raise_from(e, None)
-  File "<string>", line 3, in raise_from
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 421, in _make_request
-    httplib_response = conn.getresponse()
-  File "C:\Python39\lib\http\client.py", line 1345, in getresponse
-    response.begin()
-  File "C:\Python39\lib\http\client.py", line 307, in begin
-    version, status, reason = self._read_status()
-  File "C:\Python39\lib\http\client.py", line 268, in _read_status
-    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
-  File "C:\Python39\lib\socket.py", line 704, in readinto
-    return self._sock.recv_into(b)
-socket.timeout: timed out
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\adapters.py", line 439, in send
-    resp = conn.urlopen(
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 726, in urlopen
-    retries = retries.increment(
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\util\retry.py", line 410, in increment
-    raise six.reraise(type(error), error, _stacktrace)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\packages\six.py", line 735, in reraise
-    raise value
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 670, in urlopen
-    httplib_response = self._make_request(
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 428, in _make_request
-    self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\urllib3\connectionpool.py", line 335, in _raise_timeout
-    raise ReadTimeoutError(
-urllib3.exceptions.ReadTimeoutError: HTTPConnectionPool(host='192.168.188.57', port=8083): Read timed out. (read timeout=10)
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 225, in test_sync_invalid
-    r = requests.post(self.kobo_adress+'x/v1/auth/device', json=payload, timeout=10)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\api.py", line 119, in post
-    return request('post', url, data=data, json=json, **kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\api.py", line 61, in request
-    return session.request(method=method, url=url, **kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\sessions.py", line 530, in request
-    resp = self.send(prep, **send_kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\sessions.py", line 643, in send
-    r = adapter.send(request, **kwargs)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\requests\adapters.py", line 529, in send
-    raise ReadTimeout(e, request=request)
-requests.exceptions.ReadTimeout: HTTPConnectionPool(host='192.168.188.57', port=8083): Read timed out. (read timeout=10)</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.7" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_sync_reading_state</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.7')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.7" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.7').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 510, in test_sync_reading_state
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.8" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_sync_shelf</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.8')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.8" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.8').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 292, in test_sync_shelf
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.9" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_sync_unchanged</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.9')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.9" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.9').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 238, in test_sync_unchanged
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et20.10" class="none bg-info">
-            <td>
-                <div class='testcase'>TestKoboSync - test_sync_upload</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et20.10')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et20.10" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et20.10').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 252, in test_sync_upload
-    self.inital_sync()
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 63, in inital_sync
-    self.get_book_details(5)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 1273, in get_book_details
-    cls.driver.get(root_url + "/book/" + str(id))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="errorClass">
-        <td>_ErrorHolder</td>
-        <td class="text-center">5</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">5</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c21', 5)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="et21.1" class="none bg-info">
-            <td>
-                <div class='testcase'>tearDownClass (test_kobo_sync)</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et21.1')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et21.1" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et21.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_kobo_sync.py", line 51, in tearDownClass
-    cls.driver.get("http://127.0.0.1:8083")
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchWindowException: Message: Browsing context has been discarded</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et21.2" class="none bg-info">
-            <td>
-                <div class='testcase'>tearDownClass (test_updater)</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et21.2')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et21.2" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et21.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Python39\lib\shutil.py", line 806, in move
-    os.rename(src, real_dst)
-PermissionError: [WinError 32] Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird: 'C:\\Development\\calibre-web\\calibre-web.log' -> 'C:\\Development\\calibre-web-test\\test\\outcome\\TestUpdater-20210518195021\\calibre-web.log'
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_updater.py", line 52, in tearDownClass
-    save_logfiles(cls, cls.__name__)
-  File "C:\Development\calibre-web-test\test\helper_func.py", line 362, in save_logfiles
-    shutil.move(src,dest)
-  File "C:\Python39\lib\shutil.py", line 827, in move
-    os.unlink(src)
-PermissionError: [WinError 32] Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird: 'C:\\Development\\calibre-web\\calibre-web.log'</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et21.3" class="none bg-info">
-            <td>
-                <div class='testcase'>tearDownClass (test_user_list)</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et21.3')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et21.3" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et21.3').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Python39\lib\shutil.py", line 806, in move
-    os.rename(src, real_dst)
-PermissionError: [WinError 32] Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird: 'C:\\Development\\calibre-web\\calibre-web.log' -> 'C:\\Development\\calibre-web-test\\test\\outcome\\TestUserList-20210518200437\\calibre-web.log'
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_user_list.py", line 71, in tearDownClass
-    save_logfiles(cls, cls.__name__)
-  File "C:\Development\calibre-web-test\test\helper_func.py", line 362, in save_logfiles
-    shutil.move(src,dest)
-  File "C:\Python39\lib\shutil.py", line 827, in move
-    os.unlink(src)
-PermissionError: [WinError 32] Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird: 'C:\\Development\\calibre-web\\calibre-web.log'</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et21.4" class="none bg-info">
-            <td>
-                <div class='testcase'>tearDownClass (test_user_load)</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et21.4')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et21.4" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et21.4').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_user_load.py", line 59, in tearDownClass
-    cls.stop_calibre_web()
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 376, in stop_calibre_web
-    cls.driver.find_element_by_id('admin_stop').click()
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 360, in find_element_by_id
-    return self.find_element(by=By.ID, value=id_)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 976, in find_element
-    return self.execute(Command.FIND_ELEMENT, {
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="admin_stop"]
-
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_user_load.py", line 61, in tearDownClass
-    cls.driver.get("http://127.0.0.1:8083")
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 333, in get
-    self.execute(Command.GET, {'url': url})
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.TimeoutException: Message: TimedPromise timed out after 300000 ms</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et21.5" class="none bg-info">
-            <td>
-                <div class='testcase'>tearDownClass (test_visiblilitys)</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et21.5')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et21.5" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et21.5').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_visiblilitys.py", line 32, in tearDownClass
-    cls.stop_calibre_web()
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 378, in stop_calibre_web
-    element.click()
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webelement.py", line 80, in click
-    self._execute(Command.CLICK_ELEMENT)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webelement.py", line 633, in _execute
-    return self._parent.execute(command, params)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.ElementNotInteractableException: Message: Element <button id="shutdown" class="btn btn-default" type="button"> could not be scrolled into view</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="errorClass">
-        <td>TestLdapLogin</td>
-        <td class="text-center">13</td>
-        <td class="text-center">6</td>
-        <td class="text-center">6</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c22', 13)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt22.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_SSL</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft22.2" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_SSL_CERTIFICATE</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft22.2')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft22.2" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft22.2').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 609, in test_LDAP_SSL_CERTIFICATE
-    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et22.3" class="none bg-info">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_STARTTLS</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et22.3')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et22.3" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et22.3').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 638, in test_LDAP_STARTTLS
-    self.fill_basic_config({'config_ldap_provider_url': '127.0.0.1',
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 288, in fill_basic_config
-    cls.fill_initial_config(elements)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 201, in fill_initial_config
-    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "config_calibre_dir")))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
-    raise TimeoutException(message, screen, stacktrace)
-selenium.common.exceptions.TimeoutException: Message:</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt22.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_fallback_Login</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft22.5" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_import</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft22.5')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft22.5" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft22.5').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 401, in test_LDAP_import
-    self.assertEqual(len(userlist), 3)
-AssertionError: 4 != 3</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt22.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_import_memberfield</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft22.7" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_LDAP_login</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft22.7')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft22.7" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft22.7').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 222, in test_LDAP_login
-    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt22.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLdapLogin - test_invalid_LDAP</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt22.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLdapLogin - test_ldap_about</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft22.10" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_ldap_authentication</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft22.10')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft22.10" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft22.10').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 757, in test_ldap_authentication
-    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="ft22.11" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_ldap_kobo_sync</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft22.11')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft22.11" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft22.11').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 1024, in test_ldap_kobo_sync
-    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt22.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLdapLogin - test_ldap_opds_anonymous</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft22.13" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLdapLogin - test_ldap_opds_download_book</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft22.13')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft22.13" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft22.13').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_ldap.py", line 840, in test_ldap_opds_download_book
-    self.assertTrue(self.check_element_on_page((By.ID, "flash_success")))
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="failClass">
-        <td>TestLogging</td>
-        <td class="text-center">7</td>
-        <td class="text-center">5</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">1</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c23', 7)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt23.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogging - test_access_log_recover</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt23.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogging - test_debug_log</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt23.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogging - test_failed_login</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="st23.4" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestLogging - test_failed_register</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st23.4')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st23.4" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st23.4').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Not Implemented</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt23.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogging - test_logfile_change</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft23.6" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLogging - test_logfile_recover</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft23.6')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft23.6" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft23.6').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_logging.py", line 148, in test_logfile_recover
-    self.assertFalse(os.path.isfile(os.path.join(CALIBRE_WEB_PATH, 'calibre-web.log')))
-AssertionError: True is not false</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt23.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogging - test_logviewer</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="failClass">
-        <td>TestLogin</td>
-        <td class="text-center">14</td>
-        <td class="text-center">13</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c24', 14)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt24.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_digest_login</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_capital_letters_user_unicode_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_delete_admin</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_empty_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_locale_select</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_protected</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_remember_me</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_rename_user</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_unicode_user_space_end_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_login_user_with_space_password_end_space</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_magic_remote_login</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_next</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="ft24.13" class="none bg-danger">
-            <td>
-                <div class='testcase'>TestLogin - test_proxy_login</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft24.13')">FAIL</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_ft24.13" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_ft24.13').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_login.py", line 342, in test_proxy_login
-    self.assertTrue("Calibre-Web | login" in resp.text)
-AssertionError: False is not true</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt24.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestLogin - test_robots</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestMergeBooksList</td>
-        <td class="text-center">2</td>
-        <td class="text-center">2</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c25', 2)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt25.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestMergeBooksList - test_delete_book</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt25.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestMergeBooksList - test_merge_book</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestOAuthLogin</td>
-        <td class="text-center">2</td>
-        <td class="text-center">2</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c26', 2)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt26.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOAuthLogin - test_oauth_about</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt26.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOAuthLogin - test_visible_oauth</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestOPDSFeed</td>
-        <td class="text-center">23</td>
-        <td class="text-center">23</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c27', 23)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt27.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_author</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_books</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_calibre_companion</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_colon_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_cover</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_download_book</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_formats</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_guest_user</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_hot</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_non_admin</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_random</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_ratings</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.16' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_read_unread</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.17' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_search</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.18' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.19' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_shelf_access</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.20' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_tags</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.21' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_top_rated</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.22' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_opds_unicode_user</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt27.23' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestOPDSFeed - test_recently_added</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestUploadPDF</td>
-        <td class="text-center">1</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c28', 1)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt28.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUploadPDF - test_upload_invalid_pdf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestReader</td>
-        <td class="text-center">5</td>
-        <td class="text-center">5</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c29', 5)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt29.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestReader - test_comic_reader</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt29.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestReader - test_epub_reader</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt29.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestReader - test_pdf_reader</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt29.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestReader - test_sound_listener</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt29.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestReader - test_txt_reader</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestRegister</td>
-        <td class="text-center">8</td>
-        <td class="text-center">8</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c30', 8)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt30.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_forgot_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_illegal_email</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_limit_domain</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_register_no_server</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_registering_only_email</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_registering_user</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_registering_user_fail</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt30.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestRegister - test_user_change_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="skipClass">
-        <td>TestShelf</td>
-        <td class="text-center">12</td>
-        <td class="text-center">11</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">1</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c31', 12)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt31.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_add_shelf_from_search</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_adv_search_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_arrange_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_delete_book_of_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_private_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_public_private_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_public_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_rename_shelf</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_shelf_action_non_shelf_edit_role</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_shelf_anonymous</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="st31.11" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestShelf - test_shelf_database_change</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st31.11')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st31.11" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st31.11').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Change Database Not Implemented</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id='pt31.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestShelf - test_shelf_long_name</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="errorClass">
-        <td>TestUpdater</td>
-        <td class="text-center">8</td>
-        <td class="text-center">4</td>
-        <td class="text-center">0</td>
-        <td class="text-center">3</td>
-        <td class="text-center">1</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c32', 8)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt32.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUpdater - test_check_update_nightly_errors</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt32.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUpdater - test_check_update_nightly_request_errors</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt32.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUpdater - test_check_update_stable_errors</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt32.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUpdater - test_check_update_stable_versions</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id="et32.5" class="none bg-info">
-            <td>
-                <div class='testcase'>TestUpdater - test_perform_update</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et32.5')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et32.5" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et32.5').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_updater.py", line 364, in test_perform_update
-    self.check_element_on_page((By.ID, "DialogFinished")).click()
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webelement.py", line 80, in click
-    self._execute(Command.CLICK_ELEMENT)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webelement.py", line 633, in _execute
-    return self._parent.execute(command, params)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.ElementNotInteractableException: Message: Element <button id="DialogFinished" class="btn btn-default hidden" type="button"> could not be scrolled into view</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et32.6" class="none bg-info">
-            <td>
-                <div class='testcase'>TestUpdater - test_perform_update_stable_errors</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et32.6')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et32.6" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et32.6').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_updater.py", line 290, in test_perform_update_stable_errors
-    self.fill_basic_config({'config_updatechannel': 'Stable'})
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 288, in fill_basic_config
-    cls.fill_initial_config(elements)
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 201, in fill_initial_config
-    WebDriverWait(cls.driver, 5).until(EC.presence_of_element_located((By.ID, "config_calibre_dir")))
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\support\wait.py", line 80, in until
-    raise TimeoutException(message, screen, stacktrace)
-selenium.common.exceptions.TimeoutException: Message:</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="st32.7" class="none bg-warning">
-            <td>
-                <div class='testcase'>TestUpdater - test_perform_update_timeout</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_st32.7')">SKIP</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_st32.7" class="popup_window test_output" style="display:none;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_st32.7').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Takes too long</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-    
-        <tr id="et32.8" class="none bg-info">
-            <td>
-                <div class='testcase'>TestUpdater - test_reconnect_database</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et32.8')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et32.8" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et32.8').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">Traceback (most recent call last):
-  File "C:\Development\calibre-web-test\test\test_updater.py", line 371, in test_reconnect_database
-    self.reconnect_database()
-  File "C:\Development\calibre-web-test\test\helper_ui.py", line 362, in reconnect_database
-    self.driver.find_element_by_id('restart_database').click()
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webelement.py", line 80, in click
-    self._execute(Command.CLICK_ELEMENT)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webelement.py", line 633, in _execute
-    return self._parent.execute(command, params)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
-    self.error_handler.check_response(response)
-  File "C:\Development\calibre-web-test\test\venv\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
-    raise exception_class(message, screen, stacktrace)
-selenium.common.exceptions.ElementClickInterceptedException: Message: Element <div id="restart_database" class="btn btn-default"> is not clickable at point (555,715) because another element <div id="StatusDialog" class="modal fade in"> obscures it</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="errorClass">
-        <td>_FailedTest</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c33', 1)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id="et33.1" class="none bg-info">
-            <td>
-                <div class='testcase'>_FailedTest - test_upload_epubs</div>
-            </td>
-            <td colspan='6'>
-                <div class="text-center">
-                    <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et33.1')">ERROR</a>
-                </div>
-                <!--css div popup start-->
-                <div id="div_et33.1" class="popup_window test_output" style="display:block;">
-                    <div class='close_button pull-right'>
-                        <button type="button" class="close" aria-label="Close" onfocus='this.blur();'
-                                onclick='document.getElementById('div_et33.1').style.display='none'"><span
-                                aria-hidden="true">&times;</span></button>
-                    </div>
-                    <div class="text-left pull-left">
-                        <pre class="text-left">ImportError: Failed to import test module: test_upload_epubs
-Traceback (most recent call last):
-  File "C:\Python39\lib\unittest\loader.py", line 436, in _find_test_path
-    module = self._get_module_from_name(name)
-  File "C:\Python39\lib\unittest\loader.py", line 377, in _get_module_from_name
-    __import__(name)
-  File "C:\Development\calibre-web-test\test\test_upload_epubs.py", line 10, in <module>
-    from bs4 import BeautifulSoup
-ModuleNotFoundError: No module named 'bs4'</pre>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <!--css div popup end-->
-            </td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestUserList</td>
-        <td class="text-center">18</td>
-        <td class="text-center">18</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c34', 18)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt34.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_edit_user_email</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_list_visibility</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_admin_role</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_check_sort</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_denied_tags</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_download_role</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_button</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_email</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_kindle</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_locale</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_name</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_edit_visiblility</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_guest_edit</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_remove_admin</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.16' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_requests</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.17' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_search</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt34.18' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserList - test_user_list_sort</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestUserLoad</td>
-        <td class="text-center">1</td>
-        <td class="text-center">1</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c35', 1)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt35.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserLoad - test_user_change_vis</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestUserTemplate</td>
-        <td class="text-center">21</td>
-        <td class="text-center">21</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c36', 21)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt36.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_allow_column_restriction</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_allow_tag_restriction</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_archived_format_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_author_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_best_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_category_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_deny_column_restriction</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_deny_tag_restriction</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_detail_random_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_download_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_format_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_hot_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_language_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_limit_book_languages</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_list_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.16' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_publisher_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.17' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_random_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.18' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_read_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.19' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_recent_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.20' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_series_user_template</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt36.21' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestUserTemplate - test_ui_language_settings</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestCalibreWebVisibilitys</td>
-        <td class="text-center">34</td>
-        <td class="text-center">34</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c37', 34)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt37.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_about</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_SMTP_Settings</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_add_user</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_archived</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_authors</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_category</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_file_formats</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_hot</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_language</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_publisher</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_random</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_rated</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_rating</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_read</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.16' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_admin_change_visibility_series</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.17' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_allow_columns</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.18' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_allow_tags</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.19' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_archive_books</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.20' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_authors_max_settings</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.21' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_change_title</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.22' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_checked_logged_in</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.23' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_hide_custom_column</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.24' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_link_column_to_read_status</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.25' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_random_books_available</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.26' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_request_link_column_to_read_status</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.27' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_restrict_columns</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.28' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_restrict_tags</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.29' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_save_views_recent</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.30' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_search_functions</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.31' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_search_order</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.32' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_search_string</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.33' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_user_email_available</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt37.34' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreWebVisibilitys - test_user_visibility_sidebar</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-
-    <tr id="su" class="passClass">
-        <td>TestCalibreHelper</td>
-        <td class="text-center">16</td>
-        <td class="text-center">16</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">0</td>
-        <td class="text-center">
-            <a onclick="showClassDetail('c38', 16)">Detail</a>
-        </td>
-    </tr>
-
-    
-    
-        <tr id='pt38.1' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_author_sort</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.2' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_author_sort_comma</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.3' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_author_sort_junior</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.4' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_author_sort_oneword</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.5' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_author_sort_roman</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.6' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_Limit_Length</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.7' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_char_replacement</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.8' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_chinese_Characters</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.9' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_deg_eur_replacement</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.10' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_doubleS</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.11' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_finish_Dot</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.12' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_high23</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.13' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_check_umlauts</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.14' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_random_password</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.15' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_split_authors</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-    
-        <tr id='pt38.16' class='hiddenRow bg-success'>
-            <td>
-                <div class='testcase'>TestCalibreHelper - test_whitespaces</div>
-            </td>
-            <td colspan='6' align='center'>PASS</td>
-        </tr>
-    
-    
-
-    <tr id='total_row' class="text-center bg-grey">
-        <td>Total</td>
-        <td>344</td>
-        <td>270</td>
-        <td>14</td>
-        <td>20</td>
-        <td>40</td>
-        <td>&nbsp;</td>
+    <tr id='total_row' class="text-center bg-grey">
+        <td>Total</td>
+        <td>24</td>
+        <td>1</td>
+        <td>9</td>
+        <td>13</td>
+        <td>1</td>
+        <td>&nbsp;</td>
     </tr>
 </table>
 <div class="panel-group">
@@ -5430,13 +873,19 @@ <h4 class="panel-title">
           
             <tr>
               <th>Platform</th>
-              <td>Windows 10 10.0.19042 Intel64 Family 6 Model 165 Stepping 5, GenuineIntel AMD64</td>
+              <td>Windows 10 10.0.19041 AMD64 Family 23 Model 113 Stepping 0, AuthenticAMD AMD64</td>
               <td>Basic</td>
             </tr>
           
             <tr>
               <th>Python</th>
-              <td>3.9.5</td>
+              <td>3.8.5</td>
+              <td>Basic</td>
+            </tr>
+          
+            <tr>
+              <th>APScheduler</th>
+              <td>3.7.0</td>
               <td>Basic</td>
             </tr>
           
@@ -5454,7 +903,7 @@ <h4 class="panel-title">
           
             <tr>
               <th>Flask</th>
-              <td>1.1.4</td>
+              <td>2.0.2</td>
               <td>Basic</td>
             </tr>
           
@@ -5478,7 +927,7 @@ <h4 class="panel-title">
           
             <tr>
               <th>greenlet</th>
-              <td>1.1.0</td>
+              <td>1.1.2</td>
               <td>Basic</td>
             </tr>
           
@@ -5490,7 +939,13 @@ <h4 class="panel-title">
           
             <tr>
               <th>Jinja2</th>
-              <td>2.11.3</td>
+              <td>3.0.2</td>
+              <td>Basic</td>
+            </tr>
+          
+            <tr>
+              <th>lxml</th>
+              <td>4.6.3</td>
               <td>Basic</td>
             </tr>
           
@@ -5502,7 +957,7 @@ <h4 class="panel-title">
           
             <tr>
               <th>pytz</th>
-              <td>2021.1</td>
+              <td>2021.3</td>
               <td>Basic</td>
             </tr>
           
@@ -5512,9 +967,15 @@ <h4 class="panel-title">
               <td>Basic</td>
             </tr>
           
+            <tr>
+              <th>six</th>
+              <td>1.16.0</td>
+              <td>Basic</td>
+            </tr>
+          
             <tr>
               <th>SQLAlchemy</th>
-              <td>1.4.15</td>
+              <td>1.4.25</td>
               <td>Basic</td>
             </tr>
           
@@ -5532,184 +993,16 @@ <h4 class="panel-title">
           
             <tr>
               <th>Wand</th>
-              <td>0.6.6</td>
+              <td>0.6.7</td>
               <td>Basic</td>
             </tr>
           
             <tr>
               <th>Werkzeug</th>
-              <td>1.0.1</td>
+              <td>2.0.2</td>
               <td>Basic</td>
             </tr>
           
-            <tr>
-              <th>google-api-python-client</th>
-              <td>2.4.0</td>
-              <td>TestCliGdrivedb</td>
-            </tr>
-          
-            <tr>
-              <th>httplib2</th>
-              <td>0.19.1</td>
-              <td>TestCliGdrivedb</td>
-            </tr>
-          
-            <tr>
-              <th>lxml</th>
-              <td>4.6.3</td>
-              <td>TestCliGdrivedb</td>
-            </tr>
-          
-            <tr>
-              <th>oauth2client</th>
-              <td>4.1.3</td>
-              <td>TestCliGdrivedb</td>
-            </tr>
-          
-            <tr>
-              <th>PyDrive2</th>
-              <td>1.8.2</td>
-              <td>TestCliGdrivedb</td>
-            </tr>
-          
-            <tr>
-              <th>PyYAML</th>
-              <td>5.4.1</td>
-              <td>TestCliGdrivedb</td>
-            </tr>
-          
-            <tr>
-              <th>comicapi</th>
-              <td>2.2.0</td>
-              <td>TestEditAdditionalBooks</td>
-            </tr>
-          
-            <tr>
-              <th>lxml</th>
-              <td>4.6.3</td>
-              <td>TestEditAdditionalBooks</td>
-            </tr>
-          
-            <tr>
-              <th>rarfile</th>
-              <td>4.0</td>
-              <td>TestEditAdditionalBooks</td>
-            </tr>
-          
-            <tr>
-              <th>lxml</th>
-              <td>4.6.3</td>
-              <td>TestEditBooks</td>
-            </tr>
-          
-            <tr>
-              <th>google-api-python-client</th>
-              <td>2.4.0</td>
-              <td>TestEditBooksOnGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>httplib2</th>
-              <td>0.19.1</td>
-              <td>TestEditBooksOnGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>lxml</th>
-              <td>4.6.3</td>
-              <td>TestEditBooksOnGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>oauth2client</th>
-              <td>4.1.3</td>
-              <td>TestEditBooksOnGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>PyDrive2</th>
-              <td>1.8.2</td>
-              <td>TestEditBooksOnGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>PyYAML</th>
-              <td>5.4.1</td>
-              <td>TestEditBooksOnGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>google-api-python-client</th>
-              <td>2.4.0</td>
-              <td>TestSetupGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>httplib2</th>
-              <td>0.19.1</td>
-              <td>TestSetupGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>oauth2client</th>
-              <td>4.1.3</td>
-              <td>TestSetupGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>PyDrive2</th>
-              <td>1.8.2</td>
-              <td>TestSetupGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>PyYAML</th>
-              <td>5.4.1</td>
-              <td>TestSetupGdrive</td>
-            </tr>
-          
-            <tr>
-              <th>goodreads</th>
-              <td>0.3.2</td>
-              <td>TestGoodreads</td>
-            </tr>
-          
-            <tr>
-              <th>jsonschema</th>
-              <td>3.2.0</td>
-              <td>TestKoboSync</td>
-            </tr>
-          
-            <tr>
-              <th>Flask-SimpleLDAP</th>
-              <td>1.4.0</td>
-              <td>TestLdapLogin</td>
-            </tr>
-          
-            <tr>
-              <th>jsonschema</th>
-              <td>3.2.0</td>
-              <td>TestLdapLogin</td>
-            </tr>
-          
-            <tr>
-              <th>Flask-Dance</th>
-              <td>5.0.0</td>
-              <td>TestOAuthLogin</td>
-            </tr>
-          
-            <tr>
-              <th>SQLAlchemy-Utils</th>
-              <td>0.37.3</td>
-              <td>TestOAuthLogin</td>
-            </tr>
-          
-            <tr>
-              <th>lxml</th>
-              <td>4.6.3</td>
-              <td>TestUploadPDF</td>
-            </tr>
-          
           </tbody>
         </table>
       </div>
@@ -5718,7 +1011,7 @@ <h4 class="panel-title">
 </div>
 
 <script>
-    drawCircle(270, 14, 20, 40);
+    drawCircle(1, 9, 13, 1);
     showCase(5);
 </script>