Repository: Kinto/kinto (language: python)

Issue:
Fix bundle of kinto-admin 1.30.2, using same versions of React as upstream



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/admin.yml b/.github/workflows/admin.yml
index ffbac82a6..39d99a4e0 100644
--- a/.github/workflows/admin.yml
+++ b/.github/workflows/admin.yml
@@ -7,7 +7,7 @@ on:
 name: Kinto Admin
 jobs:
   chore:
-    name: Build Kinto Admin
+    name: Kinto Admin
     runs-on: ubuntu-latest
 
     steps:
@@ -38,3 +38,26 @@ jobs:
         run: |
           source .venv/bin/activate
           make build-kinto-admin
+
+      - name: geckodriver/firefox
+        run: |
+          echo "geckodriver/firefox"
+          which geckodriver
+          geckodriver --version
+          which firefox
+          firefox --version
+
+      - name: Install dependencies
+        run: |
+          source .venv/bin/activate
+          make install-dev
+
+      - name: Start Kinto
+        run: |
+          source .venv/bin/activate
+          kinto start --ini tests/browser.ini & sleep 5
+
+      - name: Browser Tests
+        run: |
+          source .venv/bin/activate
+          make browser-test
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8df363529..433fa3304 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,7 +6,13 @@ This document describes changes between each past release.
 14.5.1 (unreleased)
 -------------------
 
-- Nothing changed yet.
+**Bug fixes**
+
+- Fix bundle of kinto-admin, using same versions of React as upstream package #GroundhogDay
+
+**Internal Changes**
+
+- Add Selenium tests to detect Admin UI packaging issues (#2880)
 
 
 14.5.0 (2021-10-08)
diff --git a/Makefile b/Makefile
index 60535ccb5..2b523dffb 100644
--- a/Makefile
+++ b/Makefile
@@ -29,6 +29,7 @@ help:
 	@echo "  tdd                         run pytest-watch to rerun tests automatically on changes for tdd"
 	@echo "  tests-once  	             only run the tests once with the default python interpreter"
 	@echo "  functional                  run functional test against a real kinto"
+	@echo "  browser-test                run browser test against a real kinto"
 	@echo "  clean                       remove *.pyc files and __pycache__ directory"
 	@echo "  distclean                   remove *.egg-info files and *.egg, build and dist directories"
 	@echo "  maintainer-clean            remove the .tox and the .venv directories"
@@ -109,6 +110,9 @@ runkinto: install-dev
 functional: install-dev need-kinto-running
 	$(VENV)/bin/py.test tests/functional.py
 
+browser-test: need-kinto-running
+	$(VENV)/bin/py.test tests/browser.py
+
 clean:
 	find . -name '*.pyc' -delete
 	find . -name '__pycache__' -type d | xargs rm -fr
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 6571b7025..f32950500 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -7,10 +7,11 @@ pytest-cache==1.0
 pytest-cov==3.0.0
 pytest-watch==4.2.0
 python-memcached==1.59
+selenium==3.141.0
 swagger-spec-validator==2.7.3
 therapist==2.2.0
 tox==3.24.4
 WebTest==3.0.0
 wheel==0.37.0
 zest.releaser==6.22.1
-zope.sqlalchemy==1.6
+zope.sqlalchemy==1.6
\ No newline at end of file
diff --git a/docs/community.rst b/docs/community.rst
index 3d478d529..f54da34f7 100644
--- a/docs/community.rst
+++ b/docs/community.rst
@@ -223,6 +223,49 @@ For example:
     .venv/bin/pip install -e ../cornice/
 
 
+Functional Tests
+----------------
+
+In a terminal, run an instance with the provided ``functional.ini`` configuration:
+
+::
+
+    make runkinto
+
+In another terminal, run the end-to-end tests with:
+
+::
+
+    make functional
+
+
+Browser Tests
+-------------
+
+Make sure the `geckodriver <https://github.com/mozilla/geckodriver/releases>`_ binary is available in your path.
+
+.. note::
+
+    If your installation of *Firefox* is custom, specify the path of its binary using an alias:
+
+    ::
+
+        alias geckodriver="geckodriver --binary /path/to/firefox"
+
+
+In a terminal, run an instance with the provided ``browser.ini`` configuration:
+
+::
+
+    kinto start --ini tests/browser.ini
+
+In another terminal, run the end-to-end tests with:
+
+::
+
+    make browser-test
+
+
 Cleaning your environment
 -------------------------
 
diff --git a/kinto/plugins/admin/package-lock.json b/kinto/plugins/admin/package-lock.json
index 68beedd62..ed7bc2768 100644
--- a/kinto/plugins/admin/package-lock.json
+++ b/kinto/plugins/admin/package-lock.json
@@ -12,8 +12,8 @@
         "codemirror": "^5.63.1",
         "kinto-admin": "1.30.2",
         "lodash.isequalwith": "^4.4.0",
-        "react": "^17.0.2",
-        "react-dom": "^17.0.2",
+        "react": "^16.8.6",
+        "react-dom": "^16.8.6",
         "react-router": "^5.2.1",
         "redux": "^4.1.1",
         "typescript": "^4.4.3"
@@ -14564,42 +14564,6 @@
         "react": ">=15"
       }
     },
-    "node_modules/kinto-admin/node_modules/react": {
-      "version": "16.14.0",
-      "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
-      "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "prop-types": "^15.6.2"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/kinto-admin/node_modules/react-dom": {
-      "version": "16.14.0",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
-      "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "prop-types": "^15.6.2",
-        "scheduler": "^0.19.1"
-      },
-      "peerDependencies": {
-        "react": "^16.14.0"
-      }
-    },
-    "node_modules/kinto-admin/node_modules/scheduler": {
-      "version": "0.19.1",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
-      "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      }
-    },
     "node_modules/kinto-http": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/kinto-http/-/kinto-http-5.3.0.tgz",
@@ -18292,12 +18256,13 @@
       }
     },
     "node_modules/react": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
-      "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+      "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
       "dependencies": {
         "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2"
       },
       "engines": {
         "node": ">=0.10.0"
@@ -18811,16 +18776,17 @@
       "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg=="
     },
     "node_modules/react-dom": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
-      "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+      "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
       "dependencies": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1",
-        "scheduler": "^0.20.2"
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.19.1"
       },
       "peerDependencies": {
-        "react": "17.0.2"
+        "react": "^16.14.0"
       }
     },
     "node_modules/react-error-overlay": {
@@ -20221,9 +20187,9 @@
       }
     },
     "node_modules/scheduler": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
-      "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
+      "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
       "dependencies": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1"
@@ -35858,38 +35824,6 @@
         "redux-saga": "^1.1.3",
         "rimraf": "^3.0.0",
         "timeago.js": "^4.0.0"
-      },
-      "dependencies": {
-        "react": {
-          "version": "16.14.0",
-          "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
-          "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
-          "requires": {
-            "loose-envify": "^1.1.0",
-            "object-assign": "^4.1.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "react-dom": {
-          "version": "16.14.0",
-          "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
-          "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
-          "requires": {
-            "loose-envify": "^1.1.0",
-            "object-assign": "^4.1.1",
-            "prop-types": "^15.6.2",
-            "scheduler": "^0.19.1"
-          }
-        },
-        "scheduler": {
-          "version": "0.19.1",
-          "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
-          "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
-          "requires": {
-            "loose-envify": "^1.1.0",
-            "object-assign": "^4.1.1"
-          }
-        }
       }
     },
     "kinto-admin-form": {
@@ -38892,12 +38826,13 @@
       }
     },
     "react": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
-      "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+      "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
       "requires": {
         "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2"
       }
     },
     "react-app-polyfill": {
@@ -39296,13 +39231,14 @@
       "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg=="
     },
     "react-dom": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
-      "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+      "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
       "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1",
-        "scheduler": "^0.20.2"
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.19.1"
       }
     },
     "react-error-overlay": {
@@ -40413,9 +40349,9 @@
       }
     },
     "scheduler": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
-      "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
+      "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
       "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1"
diff --git a/kinto/plugins/admin/package.json b/kinto/plugins/admin/package.json
index 04e14d1b9..ec0a8b151 100644
--- a/kinto/plugins/admin/package.json
+++ b/kinto/plugins/admin/package.json
@@ -11,8 +11,8 @@
     "codemirror": "^5.63.1",
     "kinto-admin": "1.30.2",
     "lodash.isequalwith": "^4.4.0",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2",
+    "react": "^16.8.6",
+    "react-dom": "^16.8.6",
     "react-router": "^5.2.1",
     "redux": "^4.1.1",
     "typescript": "^4.4.3"


Test patch:
diff --git a/tests/browser.ini b/tests/browser.ini
new file mode 100644
index 000000000..239ca5a43
--- /dev/null
+++ b/tests/browser.ini
@@ -0,0 +1,66 @@
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = %(http_port)s
+
+
+[app:main]
+use = egg:kinto
+
+#
+# Backends.
+#
+kinto.storage_backend = kinto.core.storage.memory
+kinto.storage_url =
+kinto.cache_backend = kinto.core.cache.memory
+kinto.cache_url =
+kinto.permission_backend = kinto.core.permission.memory
+kinto.permission_url =
+
+#
+# Auth configuration.
+#
+# https://kinto.readthedocs.io/en/latest/configuration/settings.html#authentication
+#
+kinto.userid_hmac_secret = b6626ff6d28b02367127d80e2e9b596034af6c49c23eb02995dc8a76049f7b65
+multiauth.policies = account
+multiauth.policy.account.use = kinto.plugins.accounts.AccountsPolicy
+kinto.account_create_principals = system.Everyone
+
+#
+# Plugins
+#
+kinto.includes = kinto.plugins.admin
+                 kinto.plugins.accounts
+                 kinto.plugins.flush
+
+#
+# Logging configuration
+#
+
+[loggers]
+keys = root, kinto
+
+[handlers]
+keys = console
+
+[formatters]
+keys = color
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_kinto]
+level = DEBUG
+handlers = console
+qualname = kinto
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = color
+
+[formatter_color]
+class = logging_color_formatter.ColorFormatter
diff --git a/tests/browser.py b/tests/browser.py
new file mode 100644
index 000000000..8b9383012
--- /dev/null
+++ b/tests/browser.py
@@ -0,0 +1,66 @@
+import unittest
+from urllib.parse import urljoin
+
+import requests
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+SERVER_URL = "http://localhost:8888/v1"
+DEFAULT_AUTH = ("user", "p4ssw0rd")
+
+
+class BrowserTest(unittest.TestCase):
+    def setUp(self):
+        options = webdriver.FirefoxOptions()
+        options.headless = True
+        self.driver = webdriver.Firefox(options=options)
+        self.driver.implicitly_wait(10)  # seconds
+
+    @classmethod
+    def setUpClass(cls):
+        # Make sure our user exists.
+        requests.post(
+            urljoin(SERVER_URL, "/accounts"),
+            json={"data": {"id": DEFAULT_AUTH[0], "password": DEFAULT_AUTH[1]}},
+        )
+
+        # Create a bucket and a collection for our user.
+        bucket_url = urljoin(SERVER_URL, "/buckets/workspace")
+        collection_url = f"{bucket_url}/collections/articles"
+        session = requests.Session()
+        session.auth = DEFAULT_AUTH
+        resp = session.put(bucket_url)
+        resp.raise_for_status()
+        resp = session.put(collection_url)
+        resp.raise_for_status()
+
+    def tearDown(self):
+        self.driver.close()
+
+    def test_admin_ui_renders_properly(self):
+        base_url = urljoin(SERVER_URL, "/v1/admin/")
+        self.driver.get(base_url)
+
+        # Load auth page.
+        header = self.driver.find_element(By.CSS_SELECTOR, ".content div > h1")
+        self.assertIn("Administration", header.text)
+        self.assertTrue(header.is_displayed())
+
+        # Select Kinto Accounts.
+        radio = self.driver.find_element(By.CSS_SELECTOR, "input[value='accounts']")
+        radio.click()
+
+        # Fill username and password.
+        user_field = self.driver.find_element(By.ID, "root_credentials_username")
+        user_field.send_keys(DEFAULT_AUTH[0])
+        user_pass = self.driver.find_element(By.ID, "root_credentials_password")
+        user_pass.send_keys(DEFAULT_AUTH[1])
+        # Login
+        submit = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
+        submit.click()
+
+        # Navigate to simple review page (uses React Hooks and broke a few times)
+        review_url = base_url + "#/buckets/workspace/collections/articles/simple-review"
+        self.driver.get(review_url)
+        message = self.driver.find_element(By.CSS_SELECTOR, ".simple-review-blocked-message")
+        self.assertTrue(message.is_displayed())
diff --git a/tests/functional.ini b/tests/functional.ini
index 238d41d5a..72b92d32b 100644
--- a/tests/functional.ini
+++ b/tests/functional.ini
@@ -49,7 +49,7 @@ keys = root, kinto
 keys = console
 
 [formatters]
-keys = generic
+keys = color
 
 [logger_root]
 level = INFO
@@ -57,15 +57,14 @@ handlers = console
 
 [logger_kinto]
 level = DEBUG
-handlers =
+handlers = console
 qualname = kinto
-propagate = 0
 
 [handler_console]
 class = StreamHandler
 args = (sys.stderr,)
 level = NOTSET
-formatter = generic
+formatter = color
 
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+[formatter_color]
+class = logging_color_formatter.ColorFormatter