Просмотр исходного кода

ci: add ui(dashboard) tests based on pytest and selenium

Ivan Dyachkov 2 лет назад
Родитель
Сommit
8cdb1458c7

+ 12 - 2
.github/workflows/build_slim_packages.yaml

@@ -165,7 +165,7 @@ jobs:
         path: _packages/**/*
 
   docker:
-    runs-on: ubuntu-22.04
+    runs-on: aws-amd64
 
     strategy:
       fail-fast: false
@@ -196,12 +196,17 @@ jobs:
         tags: ${{ env.EMQX_IMAGE_TAG }}
         build-args: |
           EMQX_NAME=${{ env.EMQX_NAME }}
-    - name: test docker image
+    - name: smoke test
       run: |
         CID=$(docker run -d --rm -P $EMQX_IMAGE_TAG)
         HTTP_PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "18083/tcp") 0).HostPort}}' $CID)
         ./scripts/test/emqx-smoke-test.sh localhost $HTTP_PORT
         docker stop $CID
+    - name: dashboard tests
+      working-directory: ./scripts/ui-tests
+      run: |
+        set -eu
+        docker compose up --abort-on-container-exit --exit-code-from selenium
     - name: test two nodes cluster with proto_dist=inet_tls in docker
       run: |
         ./scripts/test/start-two-nodes-in-docker.sh -P $EMQX_IMAGE_TAG $EMQX_IMAGE_OLD_VERSION_TAG
@@ -216,6 +221,11 @@ jobs:
       with:
         name: "${{ matrix.profile[0] }}-docker"
         path: "${{ env.EMQX_NAME }}-${{ env.PKG_VSN }}.tar.gz"
+    - name: cleanup
+      if: always()
+      working-directory: ./scripts/ui-tests
+      run: |
+        docker compose rm -fs
 
   spellcheck:
     needs: linux

+ 15 - 0
scripts/ui-tests/conftest.py

@@ -0,0 +1,15 @@
+import pytest
+from selenium import webdriver
+
+def pytest_addoption(parser):
+    parser.addoption("--dashboard-host", action="store", default="localhost", help="Dashboard host")
+    parser.addoption("--dashboard-port", action="store", default="18083", help="Dashboard port")
+
+@pytest.fixture
+def dashboard_host(request):
+    return request.config.getoption("--dashboard-host")
+
+@pytest.fixture
+def dashboard_port(request):
+    return request.config.getoption("--dashboard-port")
+

+ 73 - 0
scripts/ui-tests/dashboard_test.py

@@ -0,0 +1,73 @@
+import time
+import unittest
+import pytest
+from urllib.parse import urljoin
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.support.wait import WebDriverWait
+from selenium.webdriver.common import utils
+
+@pytest.fixture
+def driver():
+    options = Options()
+    options.add_argument("--headless")
+    options.add_argument("--no-sandbox")
+    _driver = webdriver.Chrome(options=options)
+    yield _driver
+    _driver.quit()
+
+@pytest.fixture(autouse=True)
+def dashboard_url(dashboard_host, dashboard_port):
+    count = 0
+    while utils.is_connectable(port=dashboard_port, host=dashboard_host) is False:
+        if count == 30:
+            raise Exception("Dashboard is not ready")
+        count += 1
+        time.sleep(1)
+    return f"http://{dashboard_host}:{dashboard_port}"
+
+@pytest.fixture
+def login(driver, dashboard_url):
+    driver.get(dashboard_url)
+    assert "EMQX Dashboard" == driver.title
+    assert f"{dashboard_url}/#/login?to=/dashboard/overview" == driver.current_url
+    driver.find_element(By.XPATH, "//div[@class='login']//form[1]//input[@type='text']").send_keys("admin")
+    driver.find_element(By.XPATH, "//div[@class='login']//form[1]//input[@type='password']").send_keys("admin")
+    driver.find_element(By.XPATH, "//div[@class='login']//form[1]//button[1]").click()
+    dest_url = urljoin(dashboard_url, "/#/dashboard/overview")
+    driver.get(dest_url)
+    ensure_current_url(driver, dest_url)
+
+def ensure_current_url(driver, url):
+    count = 0
+    while url != driver.current_url:
+        if count == 10:
+            raise Exception(f"Failed to load {url}")
+        count += 1
+        time.sleep(1)
+
+def wait_title(driver):
+    return WebDriverWait(driver, 10).until(lambda x: x.find_element("xpath", "//div[@id='app']//h1[@class='header-title']")) 
+
+def test_basic(driver, login, dashboard_url):
+    driver.get(dashboard_url)
+    title = wait_title(driver)
+    assert "Cluster Overview" == title.text
+
+def test_log(driver, login, dashboard_url):
+    dest_url = urljoin(dashboard_url, "/#/log")
+    driver.get(dest_url)
+    ensure_current_url(driver, dest_url)
+    title = wait_title(driver)
+    assert "Logging" == title.text
+    label = driver.find_element(By.XPATH, "//div[@id='app']//form//label[./label/span[text()='Enable Log Handler']]")
+    assert driver.find_elements(By.ID, label.get_attribute("for"))
+    label = driver.find_element(By.XPATH, "//div[@id='app']//form//label[./label/span[text()='Log Level']]")
+    assert driver.find_elements(By.ID, label.get_attribute("for"))
+    label = driver.find_element(By.XPATH, "//div[@id='app']//form//label[./label/span[text()='Log Formatter']]")
+    assert driver.find_elements(By.ID, label.get_attribute("for"))
+    label = driver.find_element(By.XPATH, "//div[@id='app']//form//label[./label/span[text()='Time Offset']]")
+    assert driver.find_elements(By.ID, label.get_attribute("for"))
+

+ 16 - 0
scripts/ui-tests/docker-compose.yaml

@@ -0,0 +1,16 @@
+version: '3.9'
+
+services:
+  emqx:
+    image: ${EMQX_IMAGE_TAG:-emqx/emqx:latest}
+    environment:
+      EMQX_DASHBOARD__DEFAULT_PASSWORD: admin
+
+  selenium:
+    shm_size: '2gb'
+    image: ghcr.io/emqx/selenium-chrome:latest
+    volumes:
+      - ./:/app
+    depends_on:
+      - emqx
+    command: python3 -m pytest --dashboard-host emqx --dashboard-port 18083