From 8cdb1458c734d23e276334a75682da2e06edec5d Mon Sep 17 00:00:00 2001 From: Ivan Dyachkov Date: Thu, 1 Jun 2023 20:01:24 +0200 Subject: [PATCH] ci: add ui(dashboard) tests based on pytest and selenium --- .github/workflows/build_slim_packages.yaml | 14 ++++- scripts/ui-tests/conftest.py | 15 +++++ scripts/ui-tests/dashboard_test.py | 73 ++++++++++++++++++++++ scripts/ui-tests/docker-compose.yaml | 16 +++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 scripts/ui-tests/conftest.py create mode 100644 scripts/ui-tests/dashboard_test.py create mode 100644 scripts/ui-tests/docker-compose.yaml diff --git a/.github/workflows/build_slim_packages.yaml b/.github/workflows/build_slim_packages.yaml index c685078e0..b5dc01535 100644 --- a/.github/workflows/build_slim_packages.yaml +++ b/.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 diff --git a/scripts/ui-tests/conftest.py b/scripts/ui-tests/conftest.py new file mode 100644 index 000000000..d7b52eaef --- /dev/null +++ b/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") + diff --git a/scripts/ui-tests/dashboard_test.py b/scripts/ui-tests/dashboard_test.py new file mode 100644 index 000000000..4b93262b1 --- /dev/null +++ b/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")) + diff --git a/scripts/ui-tests/docker-compose.yaml b/scripts/ui-tests/docker-compose.yaml new file mode 100644 index 000000000..538db5ca8 --- /dev/null +++ b/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