name: ChangeDetection.io App Test on: workflow_call: inputs: python-version: description: 'Python version to use' required: true type: string default: '3.11' skip-pypuppeteer: description: 'Skip PyPuppeteer (not supported in 3.11/3.12)' required: false type: boolean default: false jobs: # Build the Docker image once and share it with all test jobs build: runs-on: ubuntu-latest env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: Cache pip packages uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-py${{ env.PYTHON_VERSION }}-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-py${{ env.PYTHON_VERSION }}- ${{ runner.os }}-pip- - name: Build changedetection.io container for testing under Python ${{ env.PYTHON_VERSION }} run: | echo "---- Building for Python ${{ env.PYTHON_VERSION }} -----" docker build --build-arg PYTHON_VERSION=${{ env.PYTHON_VERSION }} --build-arg LOGGER_LEVEL=TRACE -t test-changedetectionio . docker run test-changedetectionio bash -c 'pip list' - name: We should be Python ${{ env.PYTHON_VERSION }} ... run: | docker run test-changedetectionio bash -c 'python3 --version' - name: Save Docker image run: | docker save test-changedetectionio -o /tmp/test-changedetectionio.tar - name: Upload Docker image artifact uses: actions/upload-artifact@v5 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp/test-changedetectionio.tar retention-days: 1 # Unit tests (lightweight, no ancillary services needed) unit-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Run Unit Tests run: | docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_notification_diff' docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_watch_model' docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_jinja2_security' docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_semver' # Basic pytest tests with ancillary services basic-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 25 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Test built container with Pytest run: | docker network inspect changedet-network >/dev/null 2>&1 || docker network create changedet-network docker run --name test-cdio-basic-tests --network changedet-network test-changedetectionio bash -c 'cd changedetectionio && ./run_basic_tests.sh' - name: Extract memory report and logs if: always() uses: ./.github/actions/extract-memory-report with: container-name: test-cdio-basic-tests python-version: ${{ env.PYTHON_VERSION }} - name: Store test artifacts if: always() uses: actions/upload-artifact@v5 with: name: test-cdio-basic-tests-output-py${{ env.PYTHON_VERSION }} path: output-logs # Playwright tests playwright-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Spin up ancillary services run: | docker network create changedet-network docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser --hostname sockpuppetbrowser --rm -p 3000:3000 dgtlmoon/sockpuppetbrowser:latest docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser-custom-url --hostname sockpuppetbrowser-custom-url -p 3001:3000 --rm dgtlmoon/sockpuppetbrowser:latest - name: Playwright - Specific tests in built container run: | docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest -vv --capture=tee-sys --showlocals --tb=long --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_content.py' docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest -vv --capture=tee-sys --showlocals --tb=long --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_errorhandling.py' docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest -vv --capture=tee-sys --showlocals --tb=long --live-server-host=0.0.0.0 --live-server-port=5004 tests/visualselector/test_fetch_data.py' docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest -vv --capture=tee-sys --showlocals --tb=long --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_custom_js_before_content.py' - name: Playwright - Headers and requests run: | docker run --name "changedet" --hostname changedet --rm -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000?dumpio=true" --network changedet-network test-changedetectionio bash -c 'find .; cd changedetectionio; pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_request.py; pwd;find .' - name: Playwright - Restock detection run: | docker run --rm --name "changedet" -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network test-changedetectionio bash -c 'cd changedetectionio;pytest --live-server-port=5004 --live-server-host=0.0.0.0 tests/restock/test_restock.py' # Pyppeteer tests pyppeteer-tests: runs-on: ubuntu-latest needs: build if: ${{ inputs.skip-pypuppeteer == false }} timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Spin up ancillary services run: | docker network create changedet-network docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser --hostname sockpuppetbrowser --rm -p 3000:3000 dgtlmoon/sockpuppetbrowser:latest - name: Pyppeteer - Specific tests in built container run: | docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_content.py' docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_errorhandling.py' docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/visualselector/test_fetch_data.py' docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_custom_js_before_content.py' - name: Pyppeteer - Headers and requests checks run: | docker run --name "changedet" --hostname changedet --rm -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000?dumpio=true" --network changedet-network test-changedetectionio bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_request.py' - name: Pyppeteer - Restock detection run: | docker run --rm --name "changedet" -e "FLASK_SERVER_NAME=changedet" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network test-changedetectionio bash -c 'cd changedetectionio;pytest --live-server-port=5004 --live-server-host=0.0.0.0 tests/restock/test_restock.py' # Selenium tests selenium-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Spin up ancillary services run: | docker network create changedet-network docker run --network changedet-network -d --hostname selenium -p 4444:4444 --rm --shm-size="2g" selenium/standalone-chrome:4 sleep 3 - name: Specific tests for headers and requests checks with Selenium run: | docker run --name "changedet" --hostname changedet --rm -e "FLASK_SERVER_NAME=changedet" -e "WEBDRIVER_URL=http://selenium:4444/wd/hub" --network changedet-network test-changedetectionio bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_request.py' - name: Specific tests in built container for Selenium run: | docker run --rm -e "WEBDRIVER_URL=http://selenium:4444/wd/hub" --network changedet-network test-changedetectionio bash -c 'cd changedetectionio;pytest tests/fetchers/test_content.py && pytest tests/test_errorhandling.py' # SMTP tests smtp-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Spin up SMTP test server run: | docker network create changedet-network docker run --network changedet-network -d -p 11025:11025 -p 11080:11080 --hostname mailserver test-changedetectionio bash -c 'pip3 install aiosmtpd && python changedetectionio/tests/smtp/smtp-test-server.py' - name: Test SMTP notification mime types run: | docker run --rm --network changedet-network test-changedetectionio bash -c 'cd changedetectionio;pytest tests/smtp/test_notification_smtp.py' # Proxy tests proxy-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Spin up services run: | docker network create changedet-network docker run --network changedet-network -d --hostname selenium -p 4444:4444 --rm --shm-size="2g" selenium/standalone-chrome:4 docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser --hostname sockpuppetbrowser --rm -p 3000:3000 dgtlmoon/sockpuppetbrowser:latest docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser-custom-url --hostname sockpuppetbrowser-custom-url -p 3001:3000 --rm dgtlmoon/sockpuppetbrowser:latest - name: Test proxy Squid style interaction run: | cd changedetectionio ./run_proxy_tests.sh docker ps cd .. - name: Test proxy SOCKS5 style interaction run: | cd changedetectionio ./run_socks_proxy_tests.sh cd .. # Custom browser URL tests custom-browser-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Spin up ancillary services run: | docker network create changedet-network docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser --hostname sockpuppetbrowser --rm -p 3000:3000 dgtlmoon/sockpuppetbrowser:latest docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser-custom-url --hostname sockpuppetbrowser-custom-url -p 3001:3000 --rm dgtlmoon/sockpuppetbrowser:latest - name: Test custom browser URL run: | cd changedetectionio ./run_custom_browser_url_tests.sh # Container startup tests container-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Test container starts+runs basically without error run: | docker run --name test-changedetectionio -p 5556:5000 -d test-changedetectionio sleep 3 curl --retry-connrefused --retry 6 -s http://localhost:5556 |grep -q checkbox-uuid curl --retry-connrefused --retry 6 -s -g -6 "http://[::1]:5556"|grep -q checkbox-uuid docker logs test-changedetectionio 2>/dev/null | grep 'TRACE log is enabled' || exit 1 docker logs test-changedetectionio 2>/dev/null | grep 'DEBUG' || exit 1 docker kill test-changedetectionio - name: Test HTTPS SSL mode run: | openssl req -x509 -newkey rsa:4096 -keyout privkey.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" docker run --name test-changedetectionio-ssl --rm -e SSL_CERT_FILE=cert.pem -e SSL_PRIVKEY_FILE=privkey.pem -p 5000:5000 -v ./cert.pem:/app/cert.pem -v ./privkey.pem:/app/privkey.pem -d test-changedetectionio sleep 3 curl --retry-connrefused --retry 6 -k https://localhost:5000 -v|grep -q checkbox-uuid docker kill test-changedetectionio-ssl - name: Test IPv6 Mode run: | docker run --name test-changedetectionio-ipv6 --rm -p 5000:5000 -e LISTEN_HOST=:: -d test-changedetectionio sleep 3 curl --retry-connrefused --retry 6 http://[::1]:5000 -v|grep -q checkbox-uuid docker kill test-changedetectionio-ipv6 # Signal tests signal-tests: runs-on: ubuntu-latest needs: build timeout-minutes: 10 env: PYTHON_VERSION: ${{ inputs.python-version }} steps: - uses: actions/checkout@v6 - name: Download Docker image artifact uses: actions/download-artifact@v6 with: name: test-changedetectionio-${{ env.PYTHON_VERSION }} path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-changedetectionio.tar - name: Test SIGTERM and SIGINT signal shutdown run: | echo SIGINT Shutdown request test docker run --name sig-test -d test-changedetectionio sleep 3 echo ">>> Sending SIGINT to sig-test container" docker kill --signal=SIGINT sig-test sleep 3 docker ps docker logs sig-test 2>&1 | grep 'Shutdown: Got Signal - SIGINT' || exit 1 test -z "`docker ps|grep sig-test`" if [ $? -ne 0 ]; then echo "Looks like container was running when it shouldnt be" docker ps exit 1 fi docker rm sig-test echo SIGTERM Shutdown request test docker run --name sig-test -d test-changedetectionio sleep 3 echo ">>> Sending SIGTERM to sig-test container" docker kill --signal=SIGTERM sig-test sleep 3 docker ps docker logs sig-test 2>&1 | grep 'Shutdown: Got Signal - SIGTERM' || exit 1 test -z "`docker ps|grep sig-test`" if [ $? -ne 0 ]; then echo "Looks like container was running when it shouldnt be" docker ps exit 1 fi docker rm sig-test