diff --git a/.github/workflows/DailyTests.yaml b/.github/workflows/DailyTests.yaml new file mode 100644 index 0000000..bc7dca8 --- /dev/null +++ b/.github/workflows/DailyTests.yaml @@ -0,0 +1,27 @@ +name: DailyTests + +on: + schedule: + - cron: "0 4 * * *" + workflow_dispatch: + + +jobs: + run-daily-tests: + runs-on: ubuntu-22.04 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: build zimit image + run: docker build -t local-zimit . + + - name: run crawl of test website + run: docker run -v $PWD/output3:/output local-zimit zimit --url https://website.test.openzim.org/ --name tests_eng_test-website --zim-file tests_eng_test-website.zim + + - name: build selenium test image + run: docker build -t local-selenium tests-daily + + - name: run integration test suite + run: docker run -v $PWD/tests-daily/daily.py:/app/daily.py -v $PWD/output3:/output local-selenium bash -c "cd /app && pytest -h && pytest -v --log-level=INFO --log-format='%(levelname)s - %(message)s' daily.py" diff --git a/CHANGELOG.md b/CHANGELOG.md index 417d2cf..cb68082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `--custom-behaviors` argument to support path/HTTP(S) URL custom behaviors to pass to the crawler (#313) +- Add daily automated end-to-end tests of a page with Youtube player (#330) ### Changed diff --git a/pyproject.toml b/pyproject.toml index 1be4750..8424a00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ test = [ dev = [ "pre-commit==3.7.1", "debugpy==1.8.1", + "selenium==4.23.0", # used in daily tests, convenient for dev purpose (autocompletion) "zimit[scripts]", "zimit[lint]", "zimit[test]", diff --git a/tests-daily/Dockerfile b/tests-daily/Dockerfile new file mode 100644 index 0000000..f6118fe --- /dev/null +++ b/tests-daily/Dockerfile @@ -0,0 +1,75 @@ +# Let's extract kiwix-tools as usual on alpine temporary build container +FROM alpine:3.18 as kiwix-serve +LABEL org.opencontainers.image.source https://github.com/openzim/kiwix-tools + +# TARGETPLATFORM is injected by docker build +ARG TARGETPLATFORM +ARG KIWIX_TOOLS_VERSION + +RUN set -e && \ + # default (no KIWIX_TOOLS_VERSION set) to today's nightly + if [ -z "$KIWIX_TOOLS_VERSION" ] ; then KIWIX_TOOLS_VERSION=$(date +"%Y-%m-%d") ; fi && \ + apk --no-cache add dumb-init curl && \ + echo "TARGETPLATFORM: $TARGETPLATFORM" && \ + if [ "$TARGETPLATFORM" = "linux/386" ]; then ARCH="i586"; \ + # linux/arm64/v8 points to linux/arm64 + elif [ "$TARGETPLATFORM" = "linux/arm64/v8" \ + -o "$TARGETPLATFORM" = "linux/arm64" ]; then ARCH="aarch64"; \ + # linux/arm translates to linux/arm/v7 + elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then ARCH="armv8"; \ + elif [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then ARCH="armv6"; \ + elif [ "$TARGETPLATFORM" = "linux/amd64/v3" \ + -o "$TARGETPLATFORM" = "linux/amd64/v2" \ + -o "$TARGETPLATFORM" = "linux/amd64" ]; then ARCH="x86_64"; \ + # we dont suppot any other arch so let it fail + else ARCH="unknown"; fi && \ + # download requested kiwix-tools version + url="http://mirror.download.kiwix.org/nightly/$KIWIX_TOOLS_VERSION/kiwix-tools_linux-$ARCH-$KIWIX_TOOLS_VERSION.tar.gz" && \ + echo "URL: $url" && \ + mkdir /kiwix-serve && \ + curl -k -L $url | tar -xz -C /kiwix-serve --strip-components 1 + +# Build real "workload" container +FROM python:3.12-slim-bookworm + +# Add kiwix-serve +COPY --from=kiwix-serve /kiwix-serve /usr/local/bin + +# Update apt + install dependencies + install Google Chrome dependencies + clean-up apt lists +RUN apt-get update -y && \ + apt-get install -qqy wget xvfb unzip jq && \ + apt-get install -qqy libxss1 libappindicator1 libgconf-2-4 \ + fonts-liberation libasound2 libnspr4 libnss3 libx11-xcb1 libxtst6 lsb-release xdg-utils \ + libgbm1 libnss3 libatk-bridge2.0-0 libgtk-3-0 libx11-xcb1 libxcb-dri3-0 && \ + rm -rf /var/lib/apt/lists/* + +# Fetch the latest version numbers and URLs for Chrome and ChromeDriver +RUN wget -q -O /tmp/versions.json https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json + +# Install chrome +RUN CHROME_URL=$(jq -r '.channels.Stable.downloads.chrome[] | select(.platform=="linux64") | .url' /tmp/versions.json) && \ + wget -q --continue -O /tmp/chrome-linux64.zip $CHROME_URL && \ + unzip /tmp/chrome-linux64.zip -d /opt/chrome + +RUN chmod +x /opt/chrome/chrome-linux64/chrome + +# Install chromedriver +RUN CHROMEDRIVER_URL=$(jq -r '.channels.Stable.downloads.chromedriver[] | select(.platform=="linux64") | .url' /tmp/versions.json) && \ + wget -q --continue -O /tmp/chromedriver-linux64.zip $CHROMEDRIVER_URL && \ + unzip /tmp/chromedriver-linux64.zip -d /opt/chromedriver && \ + chmod +x /opt/chromedriver/chromedriver-linux64/chromedriver + +# Set up Chromedriver Environment variables +ENV CHROMEDRIVER_DIR /opt/chromedriver +ENV PATH $CHROMEDRIVER_DIR:$PATH + +# Clean up +RUN rm /tmp/chrome-linux64.zip /tmp/chromedriver-linux64.zip /tmp/versions.json + +# Update pip, install selenium, create work directory +RUN \ + python -m pip install --no-cache-dir -U \ + pip \ + selenium==4.23.0 \ + pytest==8.2.2 \ +&& mkdir -p /work diff --git a/tests-daily/daily.py b/tests-daily/daily.py new file mode 100644 index 0000000..2c1b3cf --- /dev/null +++ b/tests-daily/daily.py @@ -0,0 +1,124 @@ +import logging +import subprocess +from time import sleep + +import pytest +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.ui import WebDriverWait + +KIWIX_SERVE_START_SLEEP = 1 + +ZIM_NAME = "tests_eng_test-website" +YOUTUBE_VIDEO_PATH = "youtube.fuzzy.replayweb.page/embed/g5skcrNXdDM" + +CHECK_VIDEO_IS_PLAYING_AFTER_SECS = 30 + +logger = logging.getLogger(__name__) + + +@pytest.fixture(scope="module") +def chrome_driver(): + """Start chrome and setup chrome driver / selenium""" + + logger.info("Starting Chrome") + chrome_options = Options() + chrome_options.add_argument("--headless") + chrome_options.add_argument("--no-sandbox") + # Other options of interest: + # --disable-dev-shm-usage (not needed anymore with recent chrome versions) + # --disable-gpu (important for some versions of Chrome) + # --remote-debugging-port=9222 (should you need to remote debug) + + # Set path to Chrome binary + chrome_options.binary_location = "/opt/chrome/chrome-linux64/chrome" + + # Set path to ChromeDriver + chrome_service = ChromeService( + executable_path="/opt/chromedriver/chromedriver-linux64/chromedriver" + ) + + # Set up driver + driver = webdriver.Chrome(service=chrome_service, options=chrome_options) + + yield driver + + # Cleanup + logger.info("Quitting Chrome") + driver.quit() + + +@pytest.fixture(scope="module") +def kiwix_serve(): + """Start kiwix-serve with given ZIM""" + + logger.info("Starting kiwix-serve") + process = subprocess.Popen( + [ + "/usr/bin/env", + "/usr/local/bin/kiwix-serve", + f"/output/{ZIM_NAME}.zim", + ] + ) + + logger.info( + f"Waiting {KIWIX_SERVE_START_SLEEP} secs to be 'sure' that kiwix-serve is ready" + ) + sleep(KIWIX_SERVE_START_SLEEP) + + if process.poll() is not None: + raise Exception("kiwix-serve has terminated too early") + + yield process + + # Cleanup + logger.info("Quitting kiwix-serve") + process.terminate() + + +def test_youtube_video(chrome_driver, kiwix_serve): # noqa: ARG001 + """Test that youtube video loads, and still plays after a while""" + + chrome_driver.get(f"http://localhost:80/content/{ZIM_NAME}/{YOUTUBE_VIDEO_PATH}") + + if chrome_driver.title == "Content not found": + raise Exception("Wrong URL, kiwix-serve said that content is not found") + + button = WebDriverWait(chrome_driver, 1).until( + expected_conditions.presence_of_element_located( + (By.XPATH, "//button[@title='Play']") + ) + ) + + logger.info("Play button found in page") + + button.click() + + video = WebDriverWait(chrome_driver, 1).until( + expected_conditions.presence_of_element_located((By.TAG_NAME, "video")) + ) + + logger.info("Video found in page") + + # arguments[0] is the video tag passed to execute_script + if not chrome_driver.execute_script("return arguments[0].paused === false", video): + raise Exception("Video is not playing, failed to start probably") + + logger.info("Video is playing") + + logger.info( + f"Waiting {CHECK_VIDEO_IS_PLAYING_AFTER_SECS} secs to check video is still " + "playing" + ) + sleep(CHECK_VIDEO_IS_PLAYING_AFTER_SECS) + + # arguments[0] is the video tag passed to execute_script + if not chrome_driver.execute_script("return arguments[0].paused === false", video): + raise Exception( + "Video is not playing anymore after " + f"{CHECK_VIDEO_IS_PLAYING_AFTER_SECS} secs" + ) + logger.info("Video is still playing")