From a9b128d17e0d5849511264384e0df070c37b9765 Mon Sep 17 00:00:00 2001 From: Jan <55805868+Jaydee94@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:49:41 +0100 Subject: [PATCH] Add frontend tests with playwright (#256) * Add frontend tests with playwright The tests are written in python Add a Pipeline for github. * fix tests * fix pipeline * fix pipeline * Remove checked in vscode folder * refactor: autofix issues in 1 file Resolved issues in ui/tests/test_ui.py with DeepSource Autofix * Configure more tests location for python code * Revert "refactor: autofix issues in 1 file" This reverts commit f9970b2855c48e64c279f725628feeba1e177bca. assert in test code is OK in python, so revert this. * Simplify assertion --------- Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com> Co-authored-by: Felix Schumacher Co-authored-by: Felix Schumacher --- .deepsource.toml | 1 + .github/workflows/frontend-tests.yml | 46 ++++++ .github/workflows/main.yml | 2 +- .gitignore | 1 + api/poetry.lock | 209 ++++++++++++++++++++++----- api/pyproject.toml | 2 + ui/.env.development | 1 + ui/src/components/Secrets.vue | 39 ++++- ui/tests/test_ui.py | 169 ++++++++++++++++++++++ 9 files changed, 428 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/frontend-tests.yml create mode 100644 ui/.env.development create mode 100644 ui/tests/test_ui.py diff --git a/.deepsource.toml b/.deepsource.toml index 4357698..3332cf2 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -2,6 +2,7 @@ version = 1 test_patterns = [ "api/tests/**", + "ui/tests/**", "test_*.py" ] diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml new file mode 100644 index 0000000..32a16d3 --- /dev/null +++ b/.github/workflows/frontend-tests.yml @@ -0,0 +1,46 @@ +name: Run Python Playwright Tests +on: + pull_request: + types: + - opened + - reopened + - synchronize + push: + branches: + - main + - master + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + cd api + pip install . + + - name: Install Playwright Browsers + run: | + cd api + playwright install + + - name: Start Vue.js Development Server + run: | + cd ui + npm install + nohup npm run dev & + sleep 5 + + - name: Run Playwright Tests + run: | + cd ui + pytest tests/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 633713e..e3ea738 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: version: description: 'Contains application version' required: true - + jobs: build: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 5f94594..6995e18 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ .mypy_cache/ .pytest_cache/ .ruff_cache/ +.vscode/ venv/ app/static/.webassets-cache/ app/static/screen.css diff --git a/api/poetry.lock b/api/poetry.lock index 7b75efa..e6f0845 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -23,10 +23,8 @@ files = [ ] [package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] @@ -59,7 +57,6 @@ click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -294,26 +291,9 @@ files = [ {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - [package.extras] toml = ["tomli"] -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "fastapi" version = "0.109.2" @@ -356,6 +336,77 @@ pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "h11" version = "0.14.0" @@ -614,7 +665,6 @@ files = [ [package.dependencies] mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" [package.extras] @@ -688,6 +738,26 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] +[[package]] +name = "playwright" +version = "1.47.0" +description = "A high-level API to automate web browsers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "playwright-1.47.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f205df24edb925db1a4ab62f1ab0da06f14bb69e382efecfb0deedc4c7f4b8cd"}, + {file = "playwright-1.47.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fc820faf6885f69a52ba4ec94124e575d3c4a4003bf29200029b4a4f2b2d0ab"}, + {file = "playwright-1.47.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:8e212dc472ff19c7d46ed7e900191c7a786ce697556ac3f1615986ec3aa00341"}, + {file = "playwright-1.47.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a1935672531963e4b2a321de5aa59b982fb92463ee6e1032dd7326378e462955"}, + {file = "playwright-1.47.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0a1b61473d6f7f39c5d77d4800b3cbefecb03344c90b98f3fbcae63294ad249"}, + {file = "playwright-1.47.0-py3-none-win32.whl", hash = "sha256:1b977ed81f6bba5582617684a21adab9bad5676d90a357ebf892db7bdf4a9974"}, + {file = "playwright-1.47.0-py3-none-win_amd64.whl", hash = "sha256:0ec1056042d2e86088795a503347407570bffa32cbe20748e5d4c93dba085280"}, +] + +[package.dependencies] +greenlet = "3.0.3" +pyee = "12.0.0" + [[package]] name = "pluggy" version = "1.5.0" @@ -889,6 +959,23 @@ snowballstemmer = ">=2.2.0" [package.extras] toml = ["tomli (>=1.2.3)"] +[[package]] +name = "pyee" +version = "12.0.0" +description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"}, + {file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"] + [[package]] name = "pytest" version = "7.4.4" @@ -902,15 +989,31 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-base-url" +version = "2.1.0" +description = "pytest plugin for URL based testing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"}, + {file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"}, +] + +[package.dependencies] +pytest = ">=7.0.0" +requests = ">=2.9" + +[package.extras] +test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest-localserver (>=0.7.1)", "tox (>=3.24.5)"] + [[package]] name = "pytest-cov" version = "4.1.0" @@ -929,6 +1032,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-playwright" +version = "0.5.2" +description = "A pytest wrapper with fixtures for Playwright to automate web browsers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_playwright-0.5.2-py3-none-any.whl", hash = "sha256:2c5720591364a1cdf66610b972ff8492512bc380953e043c85f705b78b2ed582"}, + {file = "pytest_playwright-0.5.2.tar.gz", hash = "sha256:c6d603df9e6c50b35f057b0528e11d41c0963283e98c257267117f5ed6ba1924"}, +] + +[package.dependencies] +playwright = ">=1.18" +pytest = ">=6.2.4,<9.0.0" +pytest-base-url = ">=1.0.0,<3.0.0" +python-slugify = ">=6.0.0,<9.0.0" + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -957,6 +1077,23 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-slugify" +version = "8.0.4" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, + {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + [[package]] name = "pyupgrade" version = "3.17.0" @@ -1183,6 +1320,17 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + [[package]] name = "tokenize-rt" version = "6.0.0" @@ -1194,17 +1342,6 @@ files = [ {file = "tokenize_rt-6.0.0.tar.gz", hash = "sha256:b9711bdfc51210211137499b5e355d3de5ec88a85d2025c520cbb921b5194367"}, ] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -1269,5 +1406,5 @@ test = ["websockets"] [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "71a2a2e6824cb5e3a7db3473da9ae4d993e4d8f031f0017709a4e4d7367fb5b0" +python-versions = "^3.12" +content-hash = "59770345914eec24aee5c8459254ff2a366eb087b21d94866b689633f9f3c67a" diff --git a/api/pyproject.toml b/api/pyproject.toml index 6988918..d4fa62e 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -17,6 +17,8 @@ JSON-log-formatter = "^0.5.1" uvicorn = "^0.20.0" pydantic = "^2.0.0" pydantic-settings = "^2.0.3" +playwright = "^1.47.0" +pytest-playwright = "^0.5.2" [tool.poetry.group.dev.dependencies] black = "^22.8.0" diff --git a/ui/.env.development b/ui/.env.development new file mode 100644 index 0000000..fabac6c --- /dev/null +++ b/ui/.env.development @@ -0,0 +1 @@ +VITE_MOCK_NAMESPACES=true diff --git a/ui/src/components/Secrets.vue b/ui/src/components/Secrets.vue index 2e059be..fd7a9a8 100755 --- a/ui/src/components/Secrets.vue +++ b/ui/src/components/Secrets.vue @@ -414,6 +414,30 @@ const sealedSecretsAnnotations = computed(() => { return `{ sealedsecrets.bitnami.com/${scope.value}: "true" }`; }) +const adjectives = [ + "altered", "angry", "big", "blinking", "boring", "broken", "bubbling", "calculating", + "cute", "diffing", "expensive", "fresh", "fierce", "floating", "generous", "golden", + "green", "growing", "hidden", "hideous", "interesting", "kubed", "mumbling", "rusty", + "singing", "small", "sniffing", "squared", "talking", "trusty", "wise", "walking", "zooming" +]; + +const nouns = [ + "ant", "bike", "bird", "captain", "cheese", "clock", "digit", "gorilla", "kraken", "number", + "maven", "monitor", "moose", "moon", "mouse", "news", "newt", "octopus", "opossum", "otter", + "paper", "passenger", "potato", "ship", "spaceship", "spaghetti", "spoon", "store", "tomcat", + "trombone", "unicorn", "vine", "whale" +]; + +function mockNamespacesResolver(count) { + const randomPairs = new Set(); + while (randomPairs.size < count) { + const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; + const noun = nouns[Math.floor(Math.random() * nouns.length)]; + randomPairs.add(`${adjective}-${noun}`); + } + return Array.from(randomPairs).sort(); +} + function setErrorMessage(newErrorMessage) { errorMessage.value = newErrorMessage; hasErrorMessage.value = !!newErrorMessage; @@ -429,14 +453,19 @@ async function fetchConfig() { } async function fetchNamespaces(config) { - try { - const response = await fetch(`${config.api_url}/namespaces`); - namespaces.value = await response.json(); - } catch (error) { - setErrorMessage(error); + if (import.meta.env.VITE_MOCK_NAMESPACES) { + namespaces.value = mockNamespacesResolver(10); + } else { + try { + const response = await fetch(`${config.api_url}/namespaces`); + namespaces.value = await response.json(); + } catch (error) { + setErrorMessage(error); + } } } + async function fetchDisplayName(config) { displayName.value = config.display_name; } diff --git a/ui/tests/test_ui.py b/ui/tests/test_ui.py new file mode 100644 index 0000000..39814ad --- /dev/null +++ b/ui/tests/test_ui.py @@ -0,0 +1,169 @@ +import os +from playwright.sync_api import sync_playwright, Page + + +def test_ui_start(): + with sync_playwright() as ctx: + browser = ctx.chromium.launch(headless=True) + page = browser.new_page() + page.goto("http://localhost:8080") + page.wait_for_load_state("load") + assert page.title() == "Kubeseal Webgui" + browser.close() + + +def test_secret_form_with_value(): + with sync_playwright() as ctx: + browser = ctx.chromium.launch(headless=True) + page = browser.new_page() + page.goto("http://localhost:8080") + page.wait_for_load_state("load") + + disabled_encrypt_button(page) + namespace_select(page) + secret_name(page) + scope_strict(page) + add_secret_key_value(page) + click_encrypt_button(page) + + browser.close() + + +def test_secret_form_with_file(): + with sync_playwright() as ctx: + browser = ctx.chromium.launch(headless=True) + page = browser.new_page() + page.goto("http://localhost:8080") + page.wait_for_load_state("load") + + disabled_encrypt_button(page) + namespace_select(page) + secret_name(page) + scope_strict(page) + add_secret_key_file(page) + click_encrypt_button(page) + + browser.close() + + +def test_secret_form_with_invalid_file(): + with sync_playwright() as ctx: + browser = ctx.chromium.launch(headless=True) + page = browser.new_page() + page.goto("http://localhost:8080") + page.wait_for_load_state("load") + + file_input = page.locator('input[type="file"]#input-16') + # Try to upload a non-valid file + invalid_file_path = os.path.join(os.getcwd(), "large_test_file.txt") + with open(invalid_file_path, "w") as f: + f.write( + "X" * (10 * 1024 * 1024) + ) # Create a 10MB file (assuming it's too large) + file_input.set_input_files(invalid_file_path) + + # Check for an error message (adjust the selector as needed) + error_message = page.locator( + "text='File size should be less than 1 MB!'" + ) # Replace with actual error message + assert ( + error_message.is_visible() + ), "Error message should be visible for invalid file" + if os.path.exists(invalid_file_path): + os.remove(invalid_file_path) + browser.close() + + +def namespace_select(page: Page): + input_selector = "input#input-4" + page.wait_for_selector(input_selector, timeout=10000) + page.click(input_selector) + suggestions = page.query_selector_all(".v-list-item-title") + assert len(suggestions) > 0, "No suggestions found." + first_suggestion_text = suggestions[0].inner_text() + suggestions[0].click() + selected_value = page.input_value(input_selector) + assert ( + selected_value == first_suggestion_text + ), f"Expected '{first_suggestion_text}', but got '{selected_value}'" + + +def secret_name(page: Page): + input_selector = "#input-secret-name" + page.wait_for_selector(input_selector) + input_text = "valid-secret-name" + page.fill(input_selector, input_text) + assert ( + page.input_value(input_selector) == input_text + ), f"Expected {input_text}, but got {page.input_value(input_selector)}" + + +def scope_strict(page: Page): + select_selector = "div.v-select" + page.wait_for_selector(select_selector) + page.click(select_selector) + item_selector = "div.v-list-item" + page.wait_for_selector(item_selector) + items = page.query_selector_all(item_selector) + assert len(items) > 0, "No items found in the dropdown." + for item in items: + title_element = item.query_selector("div.v-list-item-title") + if title_element and title_element.inner_text().strip() == "strict": + item.click() + break + + +def add_secret_key_value(page: Page): + page.wait_for_selector("textarea#input-12") + page.fill("textarea#input-12", "my-secret-key") + assert page.locator("textarea#input-12").input_value() == "my-secret-key" + page.wait_for_selector("textarea#input-14") + page.fill("textarea#input-14", "my-secret-value") + assert page.locator("textarea#input-14").input_value() == "my-secret-value" + file_input = page.locator('input[type="file"]#input-16') + assert ( + not file_input.is_enabled() + ), "File input should be disabled when value is filled" + + +def add_secret_key_file(page: Page): + page.wait_for_selector("textarea#input-12") + page.fill("textarea#input-12", "my-secret-key") + assert page.locator("textarea#input-12").input_value() == "my-secret-key" + + file_input = page.locator('input[type="file"]#input-16') + assert file_input.is_visible() + test_file_path = os.path.join(os.getcwd(), "test_file.txt") + with open(test_file_path, "w") as f: + f.write("This is a test file.") + file_input.set_input_files(test_file_path) + if os.path.exists(test_file_path): + os.remove(test_file_path) + + +def disabled_encrypt_button(page: Page): + encrypt_button = page.locator('button:has-text("Encrypt")') + encrypt_button.wait_for() + assert encrypt_button.is_disabled() + + +def click_encrypt_button(page: Page): + # Mock the fetchEncodedSecrets function by overriding it in the browser context + page.evaluate( + """ + const appElement = document.querySelector('#app'); + const vueApp = appElement.__vue_app__; + + if (vueApp) { + vueApp._instance.proxy.fetchEncodedSecrets = function() { + window.fetchEncodedSecretsCalled = true; + }; + } + """ + ) + encrypt_button = page.locator('button:has-text("Encrypt")') + encrypt_button.wait_for() + assert encrypt_button.is_enabled() + encrypt_button.click() + is_called = page.evaluate("window.fetchEncodedSecretsCalled === true") + assert is_called, "fetchEncodedSecrets function was not called!"