diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index b16c0c0c2cb9b54384c526beb40f30e77588e675..8232937897555f2f1a8750bff8959642f9bf7f6a 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -612,6 +612,10 @@ class WebClient(http.Controller):
     def test_suite(self, mod=None, **kwargs):
         return request.render('web.qunit_suite')
 
+    @http.route('/web/tests/mobile', type='http', auth="none")
+    def test_mobile_suite(self, mod=None, **kwargs):
+        return request.render('web.qunit_mobile_suite')
+
     @http.route('/web/benchmarks', type='http', auth="none")
     def benchmarks(self, mod=None, **kwargs):
         return request.render('web.benchmark_suite')
diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js
index a214d8b55a4b364091b6875d77ce27cf849ce930..8007dc2ab7ea0bb78e01c81fcf9cdb5817203ad8 100644
--- a/addons/web/static/src/js/widgets/debug_manager.js
+++ b/addons/web/static/src/js/widgets/debug_manager.js
@@ -6,7 +6,6 @@ var dialogs = require('web.view_dialogs');
 var core = require('web.core');
 var Dialog = require('web.Dialog');
 var field_utils = require('web.field_utils');
-var framework = require('web.framework');
 var session = require('web.session');
 var SystrayMenu = require('web.SystrayMenu');
 var utils = require('web.utils');
@@ -189,6 +188,9 @@ var DebugManager = Widget.extend({
             }
         }).open();
     },
+    /**
+     * Runs the JS (desktop) tests
+     */
     perform_js_tests: function () {
         this.do_action({
             name: _t("JS Tests"),
@@ -197,6 +199,17 @@ var DebugManager = Widget.extend({
             url: '/web/tests?mod=*'
         });
     },
+    /**
+     * Runs the JS mobile tests
+     */
+    perform_js_mobile_tests: function () {
+        this.do_action({
+            name: _t("JS Mobile Tests"),
+            target: 'new',
+            type: 'ir.actions.act_url',
+            url: '/web/tests/mobile?mod=*'
+        });
+    },
     split_assets: function() {
         window.location = $.param.querystring(window.location.href, 'debug=assets');
     },
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 12f979a3c370f30919355c29f1f2d4d25fb28369..79baca71589143e5c25d6c74229456794fe3c5ee 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -220,6 +220,7 @@
 </t>
 <t t-name="WebClient.DebugManager.Global">
     <li><a href="#" data-action="perform_js_tests">Run JS Tests</a></li>
+    <li><a href="#" data-action="perform_js_mobile_tests">Run JS Mobile Tests</a></li>
     <li><a href="#" data-action="select_view">Open View</a></li>
     <t t-if="manager._events">
         <li class="divider"/>
diff --git a/addons/web/static/tests/views/kanban_mobile_tests.js b/addons/web/static/tests/views/kanban_mobile_tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..d777f5bd87b3e3ba60603a62ddc03a96f4e1493e
--- /dev/null
+++ b/addons/web/static/tests/views/kanban_mobile_tests.js
@@ -0,0 +1,101 @@
+odoo.define('web.kanban_mobile_tests', function (require) {
+"use strict";
+
+var KanbanView = require('web.KanbanView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('Views', {
+    beforeEach: function () {
+        this.data = {
+            partner: {
+                fields: {
+                    foo: {string: "Foo", type: "char"},
+                    bar: {string: "Bar", type: "boolean"},
+                    int_field: {string: "int_field", type: "integer", sortable: true},
+                    qux: {string: "my float", type: "float"},
+                    product_id: {string: "something_id", type: "many2one", relation: "product"},
+                    category_ids: { string: "categories", type: "many2many", relation: 'category'},
+                    state: { string: "State", type: "selection", selection: [["abc", "ABC"], ["def", "DEF"], ["ghi", "GHI"]]},
+                    date: {string: "Date Field", type: 'date'},
+                    datetime: {string: "Datetime Field", type: 'datetime'},
+                },
+                records: [
+                    {id: 1, bar: true, foo: "yop", int_field: 10, qux: 0.4, product_id: 3, state: "abc", category_ids: []},
+                    {id: 2, bar: true, foo: "blip", int_field: 9, qux: 13, product_id: 5, state: "def", category_ids: [6]},
+                    {id: 3, bar: true, foo: "gnap", int_field: 17, qux: -3, product_id: 3, state: "ghi", category_ids: [7]},
+                    {id: 4, bar: false, foo: "blip", int_field: -4, qux: 9, product_id: 5, state: "ghi", category_ids: []},
+                ]
+            },
+            product: {
+                fields: {
+                    id: {string: "ID", type: "integer"},
+                    name: {string: "Display Name", type: "char"},
+                },
+                records: [
+                    {id: 3, name: "hello"},
+                    {id: 5, name: "xmo"},
+                ]
+            },
+            category: {
+                fields: {
+                    name: {string: "Category Name", type: "char"},
+                    color: {string: "Color index", type: "integer"},
+                },
+                records: [
+                    {id: 6, name: "gold", color: 2},
+                    {id: 7, name: "silver", color: 5},
+                ]
+            },
+        };
+    },
+}, function () {
+
+    QUnit.module('KanbanView Mobile');
+
+    QUnit.test('mobile grouped rendering', function (assert) {
+        assert.expect(8);
+
+        var kanban = createView({
+            View: KanbanView,
+            model: 'partner',
+            data: this.data,
+            arch: '<kanban class="o_kanban_test o_kanban_small_column" on_create="quick_create">' +
+                    '<templates><t t-name="kanban-box">' +
+                        '<div><field name="foo"/></div>' +
+                    '</t></templates>' +
+                '</kanban>',
+            groupBy: ['product_id'],
+        });
+
+        // basic rendering tests
+        assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have 2 columns" );
+        assert.ok(kanban.$('.o_kanban_mobile_tab:first').hasClass('o_current'),
+            "first tab is the active tab with class 'o_current'");
+        assert.strictEqual(kanban.$('.o_kanban_group:first > div.o_kanban_record').length, 2,
+            "there are 2 records in active tab");
+        assert.strictEqual(kanban.$('.o_kanban_group:nth(1) > div.o_kanban_record').length, 0,
+            "there is no records in next tab. Records will be loaded when it will be opened");
+
+        // quick create in first column
+        kanban.$buttons.find('.o-kanban-button-new').click();
+        assert.ok(kanban.$('.o_kanban_group:nth(0) > div:nth(1)').hasClass('o_kanban_quick_create'),
+            "clicking on create should open the quick_create in the first column");
+
+        // move to second column
+        kanban.$('.o_kanban_mobile_tab:nth(1)').trigger('click');
+        assert.ok(kanban.$('.o_kanban_mobile_tab:nth(1)').hasClass('o_current'),
+            "second tab is now active with class 'o_current'");
+        assert.strictEqual(kanban.$('.o_kanban_group:nth(1) > div.o_kanban_record').length, 2,
+            "the 2 records of the second group have now been loaded");
+
+        // quick create in second column
+        kanban.$buttons.find('.o-kanban-button-new').click();
+        assert.ok(kanban.$('.o_kanban_group:nth(1) >  div:nth(1)').hasClass('o_kanban_quick_create'),
+            "clicking on create should open the quick_create in the second column");
+
+        kanban.destroy();
+    });
+});
+});
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index cfd9e1210ba9e82e2134f029ab77f9d7c010c1a2..f614fa9af64ba8462ee75e3977a292317ffdcbfc 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -2129,55 +2129,6 @@ QUnit.module('Views', {
                 "quick create should have been added in the first column");
         }
     });
-
-    QUnit.skip('mobile grouped rendering', function (assert) {
-        // Temporarily disable this test until we introduce a mobile test suite, as
-        // the code of the kanban renderer for mobile is in a specific file which isn't
-        // executed in desktop (so setting 'isMobile: True' in the test is useless).
-        // So to re-activate this test, it will need to be moved in another file
-        // included in the bundle of the mobile test suite.
-        assert.expect(8);
-        var done = assert.async();
-
-        createAsyncView({
-            View: KanbanView,
-            model: 'partner',
-            data: this.data,
-            arch: '<kanban class="o_kanban_test o_kanban_small_column" on_create="quick_create">' +
-                    '<templates><t t-name="kanban-box">' +
-                        '<div><field name="foo"/></div>' +
-                    '</t></templates>' +
-                '</kanban>',
-            groupBy: ['product_id'],
-            config: {device: {isMobile: true}},
-        }).then(function (kanban) {
-
-            // Dummy dom update trigger for activate mobile tabs and move to first column
-            core.bus.trigger("DOM_updated");
-
-            assert.equal(kanban.$el.find('.o_kanban_group').length, 2, "2 colomns are created" );
-
-            kanban.$buttons.find('.o-kanban-button-new').click(); // Click on 'Create'
-            assert.ok(kanban.$('.o_kanban_group:nth(0) > div:nth(1)').hasClass('o_kanban_quick_create'),
-                "clicking on create should open the quick_create in the first column");
-
-            assert.equal(kanban.$el.find('.o_kanban_mobile_tab.current > span').html(), "hello", "First tab 'hello' is active tab with class 'current'" );
-            assert.equal(kanban.$el.find('.o_kanban_group.current > div.o_kanban_record').length, "2", "there is 2 record in active 'hello' tab" );
-            assert.equal(kanban.$el.find('.o_kanban_group.next > div.o_kanban_record').length, "0", "there is 0 record in next tab. Records will load when click on next tab");
-
-            kanban.$el.find('.o_kanban_mobile_tab.next').trigger('click'); // Moving to next tab
-            assert.equal(kanban.$el.find('.o_kanban_mobile_tab.current > span').html(), "xmo", "Second tab 'xmo' is active with class 'current'" );
-            assert.equal(kanban.$el.find('.o_kanban_group.current > div.o_kanban_record').length, "2", "there is 2 record in active 'xmo' tab. Records are loaded after click on tab");
-
-            kanban.$buttons.find('.o-kanban-button-new').click(); // Click on 'Create'
-            assert.ok(kanban.$('.o_kanban_group:nth(1) >  div:nth(1)').hasClass('o_kanban_quick_create'),
-                "clicking on create should open the quick_create in the second column");
-
-            kanban.destroy();
-            done();
-        });
-    });
-
 });
 
 });
diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py
index 17eb5c8beb2f4aa28a659c04ec468f162f725fd9..0b1f8ed3763c7068a8285e4448d083431a293d47 100644
--- a/addons/web/tests/test_js.py
+++ b/addons/web/tests/test_js.py
@@ -11,8 +11,13 @@ class WebSuite(odoo.tests.HttpCase):
     at_install = False
 
     def test_01_js(self):
+        # webclient desktop test suite
         self.phantom_js('/web/tests?mod=web', "", "", login='admin', timeout=300)
 
+    def test_02_js(self):
+        # webclient mobile test suite
+        self.phantom_js('/web/tests/mobile?mod=web', "", "", login='admin', timeout=300)
+
     def test_check_suite(self):
         # verify no js test is using `QUnit.only` as it forbid any other test to be executed
         re_only = re.compile('QUnit\.only\(')
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 77a5c4bedba77b77e951fb8d41fc0e1ef711a5f4..5117d1e633c8ccc5e6eff831074871100afa37f5 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -396,60 +396,67 @@
         </a>
     </template>
 
+    <template id="web.js_tests_assets">
+        <link type="text/css" rel="stylesheet" href="/web/static/lib/qunit/qunit-2.2.1.css"/>
+        <script type="text/javascript" src="/web/static/lib/qunit/qunit-2.2.1.js"></script>
+        <script type="text/javascript" src="/web/static/tests/helpers/qunit_config.js"></script>
+
+        <t t-call-assets="web.assets_common" t-js="false"/>
+        <t t-call-assets="web.assets_backend" t-js="false"/>
+        <t t-call-assets="web.assets_common" t-css="false"/>
+        <t t-call-assets="web.assets_backend" t-css="false"/>
+
+        <!-- add lazy-loaded libs to make tests synchronous -->
+        <link rel="stylesheet" href="/web/static/lib/fullcalendar/css/fullcalendar.css"/>
+        <script type="text/javascript" src="/web/static/lib/fullcalendar/js/fullcalendar.js"></script>
+        <link rel="stylesheet" type="text/css" href="/web/static/lib/nvd3/nv.d3.css"/>
+        <script type="text/javascript" src="/web/static/lib/nvd3/d3.v3.js"></script>
+        <script type="text/javascript" src="/web/static/lib/nvd3/nv.d3.js"></script>
+        <script type="text/javascript" src="/web/static/src/js/libs/nvd3.js"></script>
+        <script type="text/javascript" src="/web/static/lib/ace/ace.odoo-custom.js"></script>
+        <script type="text/javascript" src="/web/static/lib/ace/mode-python.js"></script>
+        <script type="text/javascript" src="/web/static/lib/ace/mode-xml.js"></script>
+
+        <script type="text/javascript">
+            // define the 'web.web_client' module because some other modules require it
+            odoo.define('web.web_client', function (require) {
+                var WebClient = require('web.WebClient');
+                var web_client = new WebClient();
+                // override _call_service to prevent the web_client from doing RPCs
+                web_client._call_service = function () {};
+                return web_client;
+            });
+        </script>
+
+        <style>
+            body {
+                position: relative; // bootstrap-datepicker needs this
+            }
+            body:not(.debug) .modal-backdrop, body:not(.debug) .modal, body:not(.debug) .ui-autocomplete {
+                opacity: 0 !important;
+            }
+            #qunit-testrunner-toolbar label {
+                font-weight: inherit;
+                margin-bottom: inherit;
+            }
+            #qunit-testrunner-toolbar input[type=text] {
+                width: inherit;
+                display: inherit;
+            }
+        </style>
+
+        <script type="text/javascript" src="/web/static/tests/helpers/test_utils.js"></script>
+        <script type="text/javascript" src="/web/static/tests/helpers/mock_server.js"></script>
+
+        <script type="text/javascript" src="/web/static/tests/boot_tests.js"></script>
+    </template>
+
     <template id="web.qunit_suite">
         <t t-call="web.layout">
             <t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
             <t t-set="title">Web Tests</t>
             <t t-set="head">
-                <link type="text/css" rel="stylesheet" href="/web/static/lib/qunit/qunit-2.2.1.css"/>
-                <script type="text/javascript" src="/web/static/lib/qunit/qunit-2.2.1.js"></script>
-                <script type="text/javascript" src="/web/static/tests/helpers/qunit_config.js"></script>
-
-                <t t-call-assets="web.assets_common" t-js="false"/>
-                <t t-call-assets="web.assets_backend" t-js="false"/>
-                <t t-call-assets="web.assets_common" t-css="false"/>
-                <t t-call-assets="web.assets_backend" t-css="false"/>
-
-                <!-- add lazy-loaded libs -->
-                <link rel="stylesheet" href="/web/static/lib/fullcalendar/css/fullcalendar.css"/>
-                <script type="text/javascript" src="/web/static/lib/fullcalendar/js/fullcalendar.js"></script>
-                <link rel="stylesheet" type="text/css" href="/web/static/lib/nvd3/nv.d3.css"/>
-                <script type="text/javascript" src="/web/static/lib/nvd3/d3.v3.js"></script>
-                <script type="text/javascript" src="/web/static/lib/nvd3/nv.d3.js"></script>
-                <script type="text/javascript" src="/web/static/src/js/libs/nvd3.js"></script>
-                <script type="text/javascript" src="/web/static/lib/ace/ace.odoo-custom.js"></script>
-                <script type="text/javascript" src="/web/static/lib/ace/mode-python.js"></script>
-                <script type="text/javascript" src="/web/static/lib/ace/mode-xml.js"></script>
-
-                <script type="text/javascript">
-                    // define the 'web.web_client' module because some other modules require it
-                    odoo.define('web.web_client', function (require) {
-                        var WebClient = require('web.WebClient');
-                        var web_client = new WebClient();
-                        // override _call_service to prevent the web_client from doing RPCs
-                        web_client._call_service = function () {};
-                        return web_client;
-                    });
-                </script>
-
-                <style>
-                    body {
-                        position: relative; // bootstrap-datepicker needs this
-                    }
-                    body:not(.debug) .modal-backdrop, body:not(.debug) .modal, body:not(.debug) .ui-autocomplete {
-                        opacity: 0 !important;
-                    }
-                    #qunit-testrunner-toolbar label {
-                        font-weight: inherit;
-                        margin-bottom: inherit;
-                    }
-                    #qunit-testrunner-toolbar input[type=text] {
-                        width: inherit;
-                        display: inherit;
-                    }
-                </style>
-                <script type="text/javascript" src="/web/static/tests/helpers/test_utils.js"></script>
-                <script type="text/javascript" src="/web/static/tests/helpers/mock_server.js"></script>
+                <t t-call="web.js_tests_assets"/>
 
                 <script type="text/javascript" src="/web/static/tests/fields/basic_fields_tests.js"></script>
                 <script type="text/javascript" src="/web/static/tests/fields/field_utils_tests.js"></script>
@@ -486,8 +493,45 @@
                 <script type="text/javascript" src="/web/static/tests/widgets/domain_selector_tests.js"/>
                 <script type="text/javascript" src="/web/static/tests/widgets/model_field_selector_tests.js"/>
                 <script type="text/javascript" src="/web/static/tests/widgets/rainbow_man_tests.js"/>
+            </t>
+
+            <div id="qunit"/>
+            <div id="qunit-fixture"/>
+        </t>
+    </template>
+
+    <template id="web.qunit_mobile_suite">
+        <t t-call="web.layout">
+            <t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
+            <t t-set="title">Web Mobile Tests</t>
+            <t t-set="head">
+                <script>
+                    // force the config.device.isMobile key to be true so that
+                    // mobile specific files aren't rejected
+                    window.odoo = {};
+                    var odooDefine;
+                    Object.defineProperty(window.odoo, 'define', {
+                        get: function () {
+                            return odooDefine;
+                        },
+                        set: function (define) {
+                            odooDefine = function () {
+                                define.apply(this, arguments);
+                                if (arguments[0] === 'web.config') {
+                                    define.call(this, 'web.config.patch', function (require) {
+                                        var config = require('web.config');
+                                        config.device.isMobile = true;
+                                    });
+                                }
+                            };
+                        },
+                    });
+                </script>
+
+                <t t-call="web.js_tests_assets"/>
+                <script type="text/javascript" src="/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js"></script>
 
-                <script type="text/javascript" src="/web/static/tests/boot_tests.js"></script>
+                <script type="text/javascript" src="/web/static/tests/views/kanban_mobile_tests.js"></script>
             </t>
 
             <div id="qunit"/>
diff --git a/addons/web_editor/views/editor.xml b/addons/web_editor/views/editor.xml
index 8b7b51944a592b8882db586744e7261c2217b7b3..4bfe84887d02abcea4b931066f8cd1da56fe0e57 100644
--- a/addons/web_editor/views/editor.xml
+++ b/addons/web_editor/views/editor.xml
@@ -114,10 +114,14 @@
         <t t-call-assets="web_editor.assets_editor" t-css="false"/>
     </xpath>
 </template>
-<template id="qunit_suite" inherit_id="web.qunit_suite">
-    <xpath expr="//t[@t-call-assets='web.assets_backend'][@t-css='false']" position="after">
+<template id="js_tests_assets" inherit_id="web.js_tests_assets">
+    <xpath expr="." position="inside">
         <t t-call-assets="web_editor.summernote" t-css="false"/>
         <t t-call-assets="web_editor.assets_editor" t-css="false"/>
+    </xpath>
+</template>
+<template id="qunit_suite" inherit_id="web.qunit_suite">
+    <xpath expr="." position="inside">
         <script type="text/javascript" src="/web_editor/static/tests/web_editor_tests.js"></script>
     </xpath>
 </template>