diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e16fc2478..b17a01329 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ --- -# Run basic tests for this app on the latest aiidalab-docker image. +# Run basic tests for this app name: continuous-integration @@ -13,16 +13,21 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: '3.10' + cache: pip + cache-dependency-path: | + .pre-commit-config.yaml + **/setup.cfg + **/pyproject.toml + **/requirements*.txt - name: Install dependencies - run: | - python -m pip install pre-commit==2.11.1 + run: python -m pip install pre-commit~=2.20 - name: Run pre-commit run: pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) diff --git a/.github/workflows/di.yml b/.github/workflows/di.yml index 56aa65b85..9fd7136d7 100644 --- a/.github/workflows/di.yml +++ b/.github/workflows/di.yml @@ -16,9 +16,8 @@ jobs: strategy: matrix: tag: [latest] + image: [aiidalab/full-stack] browser: [Chrome, Firefox] - python-version: ['3.8', '3.10'] - firefox: ['96.0'] fail-fast: false runs-on: ubuntu-latest @@ -26,23 +25,20 @@ jobs: steps: - name: Check out app - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - name: Cache Python dependencies - uses: actions/cache@v1 + - name: Set up Python + uses: actions/setup-python@v4 with: - path: ~/.cache/pip - key: pip-${{ matrix.python-version }}-tests-${{ hashFiles('**/setup.json') }} - restore-keys: pip-${{ matrix.python-version }}-tests - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} + python-version: '3.10' + cache: pip + cache-dependency-path: | + **/setup.cfg + **/pyproject.toml + **/requirements*.txt - name: Install dependencies for test - run: | - pip install -U -r requirements_test.txt + run: pip install -U -r requirements_test.txt - name: Set jupyter token env run: echo "JUPYTER_TOKEN=$(openssl rand -hex 32)" >> $GITHUB_ENV @@ -52,7 +48,7 @@ jobs: - name: Install Firefox uses: browser-actions/setup-firefox@latest with: - firefox-version: ${{ matrix.firefox }} + firefox-version: '96.0' if: matrix.browser == 'Firefox' - name: Install geckodriver @@ -65,3 +61,12 @@ jobs: run: pytest --driver ${{ matrix.browser }} env: TAG: ${{ matrix.tag }} + AIIDALAB_IMAGE: ${{ matrix.image }} + + - name: Upload screenshots as artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: Screenshots-${{ matrix.tag }}-${{ matrix.browser }} + path: screenshots/ + if-no-files-found: error diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca0e05cc5..6e1b81be7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-json - id: check-yaml @@ -22,7 +22,7 @@ repos: language_version: python3 # Should be a command that runs python3.6+ - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 args: [--count, --show-source, --statistics] @@ -35,17 +35,12 @@ repos: - id: isort args: [--profile, black, --filter-files] - - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 - hooks: - - id: setup-cfg-fmt - - repo: https://github.com/sirosen/check-jsonschema rev: 0.19.2 hooks: - id: check-github-workflows - repo: https://github.com/kynan/nbstripout - rev: 0.5.0 + rev: 0.6.1 hooks: - id: nbstripout diff --git a/pyproject.toml b/pyproject.toml index 374b58cbf..1eb0ca77d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - "setuptools>=42", + "setuptools>=62.6", "wheel" ] build-backend = "setuptools.build_meta" diff --git a/requirements_test.txt b/requirements_test.txt index 5c92ac0cd..bab7a8fef 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ -pytest~=6.0 +pytest~=6.2 pytest-docker~=1.0 pytest-selenium~=4.0 webdriver-manager~=3.8 -selenium==4.1.0 +selenium~=4.7.0 diff --git a/setup.cfg b/setup.cfg index be0d8e0b8..de7912afc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,9 +36,8 @@ python_requires = >=3.8 [options.extras_require] dev = bumpver==2022.1119 - pre-commit==2.11.1 -test = - requirements-test.txt + pre-commit~=2.20 +test = file: requirements_test.txt [options.package_data] aiidalab_qe.parameters = qeapp.yaml @@ -51,9 +50,9 @@ categories = [flake8] ignore = - E501 # Line length handled by black. - W503 # Line break before binary operator, preferred formatting for black. - E203 # Whitespace before ':', preferred formatting for black. + E501 + W503 + E203 [bumpver] current_version = "v22.12.0" diff --git a/tests/conftest.py b/tests/conftest.py index de7ff6347..6eb77e927 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from urllib.parse import urljoin import pytest @@ -17,24 +18,45 @@ def is_responsive(url): @pytest.fixture(scope="session") -def notebook_service(docker_ip, docker_services): - """Ensure that HTTP service is up and responsive.""" +def docker_compose(docker_services): + return docker_services._docker_compose - docker_compose = docker_services._docker_compose - # assurance for host user UID other that 1000 - chown_command = "exec -T -u root aiidalab bash -c 'chown -R jovyan:users /home/jovyan/apps/aiidalab-qe'" - docker_compose.execute(chown_command) +@pytest.fixture(scope="session") +def aiidalab_exec(docker_compose): + def execute(command, user=None, workdir=None, **kwargs): + opts = "-T" + if user: + opts = f"{opts} --user={user}" + if workdir: + opts = f"{opts} --workdir={workdir}" + command = f"exec {opts} aiidalab {command}" - install_command = "bash -c 'pip install -U .'" - command = f"exec --workdir /home/jovyan/apps/aiidalab-qe/src -T aiidalab {install_command}" - docker_compose.execute(command) + return docker_compose.execute(command, **kwargs) - install_command = "bash -c 'python tests/helper_dep_requirements.py && pip install -U -r /tmp/requirements.txt'" - command = ( - f"exec --workdir /home/jovyan/apps/aiidalab-qe -T aiidalab {install_command}" - ) - docker_compose.execute(command) + return execute + + +@pytest.fixture(scope="session") +def nb_user(aiidalab_exec): + return aiidalab_exec("bash -c 'echo \"${NB_USER}\"'").decode().strip() + + +@pytest.fixture(scope="session") +def notebook_service(docker_ip, docker_services, aiidalab_exec, nb_user): + """Ensure that HTTP service is up and responsive.""" + + # Directory ~/apps/aiidalab-qe/ is mounted by docker, + # make it writeable for jovyan user, needed for `pip install` + appdir = f"/home/{nb_user}/apps/aiidalab-qe" + aiidalab_exec(f"chmod -R a+rw {appdir}", user="root") + + # Install workchains + aiidalab_exec("pip install .", workdir=f"{appdir}/src", user=nb_user) + + # Install App + install_command = "bash -c 'python tests/helper_dep_requirements.py && pip install -r /tmp/requirements.txt'" + aiidalab_exec(install_command, workdir=appdir, user=nb_user) # `port_for` takes a container port and returns the corresponding host port port = docker_services.port_for("aiidalab", 8888) @@ -62,6 +84,16 @@ def _selenium_driver(nb_path, wait_time=5.0): return _selenium_driver +@pytest.fixture(scope="session") +def screenshot_dir(): + sdir = Path.joinpath(Path.cwd(), "screenshots") + try: + os.mkdir(sdir) + except FileExistsError: + pass + return sdir + + @pytest.fixture def firefox_options(firefox_options): firefox_options.add_argument("--headless") diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 4f0442961..48da72e36 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -4,7 +4,7 @@ version: '3.4' services: aiidalab: - image: aiidalab/full-stack:${TAG:-latest} + image: ${AIIDALAB_IMAGE:-aiidalab/full-stack}:${TAG:-latest} environment: RMQHOST: messaging TZ: Europe/Zurich diff --git a/tests/helper_dep_requirements.py b/tests/helper_dep_requirements.py index 8b0647034..969f8ad40 100644 --- a/tests/helper_dep_requirements.py +++ b/tests/helper_dep_requirements.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""This helper script is for temporarily remove the -`aiidalab-qe-workchain` package from dependencies list so that +"""This helper script removes the `aiidalab-qe-workchain` package +from dependencies list so that in the test it will not use the remote released source. """ diff --git a/tests/test_qe_app.py b/tests/test_qe_app.py index 7af7f747d..27bc0e1e0 100755 --- a/tests/test_qe_app.py +++ b/tests/test_qe_app.py @@ -1,3 +1,6 @@ +import time +from pathlib import Path + import requests from selenium.webdriver.common.by import By @@ -8,21 +11,29 @@ def test_notebook_service_available(notebook_service): assert response.status_code == 200 -def test_qe_app_take_screenshot(selenium_driver): +def test_qe_app_take_screenshot(selenium_driver, screenshot_dir): driver = selenium_driver("qe.ipynb", wait_time=30.0) - driver.set_window_size(1920, 985) - driver.get_screenshot_as_file("screenshots/qe-app.png") + driver.set_window_size(1920, 1485) + time.sleep(15) + driver.get_screenshot_as_file(str(Path.joinpath(screenshot_dir, "qe-app.png"))) -def test_qe_app_select_silicon(selenium_driver): +def test_qe_app_select_silicon(selenium_driver, screenshot_dir): driver = selenium_driver("qe.ipynb", wait_time=30.0) - driver.set_window_size(1920, 985) + driver.set_window_size(1920, 1485) driver.find_element( By.XPATH, "//*[text()='From Examples']" ).click() # click `From Examples` tab for input structure driver.find_element(By.XPATH, "//option[@value='Diamond']").click() - driver.get_screenshot_as_file("screenshots/qe-app-select-diamond-selected.png") + time.sleep(2) + driver.get_screenshot_as_file( + str(Path.joinpath(screenshot_dir, "qe-app-select-diamond-selected.png")) + ) confirm_button = driver.find_element(By.XPATH, "//button[text()='Confirm']") confirm_button.location_once_scrolled_into_view # scroll into view confirm_button.click() - driver.get_screenshot_as_file("screenshots/qe-app-select-diamond-confirmed.png") + # Test that we have indeed proceeded to the next step + driver.find_element(By.XPATH, "//span[contains(.,'✓ Step 1')]") + driver.get_screenshot_as_file( + str(Path.joinpath(screenshot_dir, "qe-app-select-diamond-confirmed.png")) + )