mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-21 17:06:09 +00:00
Compare commits
7 Commits
playwright
...
0.39.21.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0efeb9770 | ||
|
|
3416532cd6 | ||
|
|
defc7a340e | ||
|
|
c197c062e1 | ||
|
|
77b59809ca | ||
|
|
f90b170e68 | ||
|
|
c93ca1841c |
31
.github/test/Dockerfile-alpine
vendored
Normal file
31
.github/test/Dockerfile-alpine
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Taken from https://github.com/linuxserver/docker-changedetection.io/blob/main/Dockerfile
|
||||||
|
# Test that we can still build on Alpine (musl modified libc https://musl.libc.org/)
|
||||||
|
# Some packages wont install via pypi because they dont have a wheel available under this architecture.
|
||||||
|
|
||||||
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.16
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
COPY requirements.txt /requirements.txt
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apk add --update --no-cache --virtual=build-dependencies \
|
||||||
|
cargo \
|
||||||
|
g++ \
|
||||||
|
gcc \
|
||||||
|
libc-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libxslt-dev \
|
||||||
|
make \
|
||||||
|
openssl-dev \
|
||||||
|
py3-wheel \
|
||||||
|
python3-dev \
|
||||||
|
zlib-dev && \
|
||||||
|
apk add --update --no-cache \
|
||||||
|
libxslt \
|
||||||
|
python3 \
|
||||||
|
py3-pip && \
|
||||||
|
echo "**** pip3 install test of changedetection.io ****" && \
|
||||||
|
pip3 install -U pip wheel setuptools && \
|
||||||
|
pip3 install -U --no-cache-dir --find-links https://wheel-index.linuxserver.io/alpine-3.16/ -r /requirements.txt && \
|
||||||
|
apk del --purge \
|
||||||
|
build-dependencies
|
||||||
11
.github/workflows/test-container-build.yml
vendored
11
.github/workflows/test-container-build.yml
vendored
@@ -43,6 +43,16 @@ jobs:
|
|||||||
version: latest
|
version: latest
|
||||||
driver-opts: image=moby/buildkit:master
|
driver-opts: image=moby/buildkit:master
|
||||||
|
|
||||||
|
# https://github.com/dgtlmoon/changedetection.io/pull/1067
|
||||||
|
# Check we can still build under alpine/musl
|
||||||
|
- name: Test that the docker containers can build (musl via alpine check)
|
||||||
|
id: docker_build_musl
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
file: ./.github/test/Dockerfile-alpine
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
- name: Test that the docker containers can build
|
- name: Test that the docker containers can build
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
@@ -53,3 +63,4 @@ jobs:
|
|||||||
platforms: linux/arm/v7,linux/arm/v6,linux/amd64,linux/arm64,
|
platforms: linux/arm/v7,linux/arm/v6,linux/amd64,linux/arm64,
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ COPY requirements.txt /requirements.txt
|
|||||||
|
|
||||||
RUN pip install --target=/dependencies -r /requirements.txt
|
RUN pip install --target=/dependencies -r /requirements.txt
|
||||||
|
|
||||||
RUN pip install --target=/dependencies jq~=1.3 \
|
# Playwright is an alternative to Selenium
|
||||||
|| echo "WARN: Failed to install JQ. The application can still run, but the Jq: filter option will be disabled."
|
# Excluded this package from requirements.txt to prevent arm/v6 and arm/v7 builds from failing
|
||||||
|
# https://github.com/dgtlmoon/changedetection.io/pull/1067 also musl/alpine (not supported)
|
||||||
|
RUN pip install --target=/dependencies playwright~=1.26 \
|
||||||
|
|| echo "WARN: Failed to install Playwright. The application can still run, but the Playwright option will be disabled."
|
||||||
|
|
||||||
# Final image stage
|
# Final image stage
|
||||||
FROM python:3.8-slim
|
FROM python:3.8-slim
|
||||||
|
|||||||
@@ -167,9 +167,6 @@ One big advantage of `jq` is that you can use logic in your JSON filter, such as
|
|||||||
|
|
||||||
See the wiki https://github.com/dgtlmoon/changedetection.io/wiki/JSON-Selector-Filter-help for more information and examples
|
See the wiki https://github.com/dgtlmoon/changedetection.io/wiki/JSON-Selector-Filter-help for more information and examples
|
||||||
|
|
||||||
Note: `jq` library must be added separately (`pip3 install jq`)
|
|
||||||
|
|
||||||
|
|
||||||
### Parse JSON embedded in HTML!
|
### Parse JSON embedded in HTML!
|
||||||
|
|
||||||
When you enable a `json:` or `jq:` filter, you can even automatically extract and parse embedded JSON inside a HTML page! Amazingly handy for sites that build content based on JSON, such as many e-commerce websites.
|
When you enable a `json:` or `jq:` filter, you can even automatically extract and parse embedded JSON inside a HTML page! Amazingly handy for sites that build content based on JSON, such as many e-commerce websites.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from flask_wtf import CSRFProtect
|
|||||||
from changedetectionio import html_tools
|
from changedetectionio import html_tools
|
||||||
from changedetectionio.api import api_v1
|
from changedetectionio.api import api_v1
|
||||||
|
|
||||||
__version__ = '0.39.20.4'
|
__version__ = '0.39.21.1'
|
||||||
|
|
||||||
datastore = None
|
datastore = None
|
||||||
|
|
||||||
@@ -199,8 +199,6 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Setup cors headers to allow all domains
|
# Setup cors headers to allow all domains
|
||||||
# https://flask-cors.readthedocs.io/en/latest/
|
# https://flask-cors.readthedocs.io/en/latest/
|
||||||
# CORS(app)
|
# CORS(app)
|
||||||
@@ -1309,8 +1307,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
threading.Thread(target=notification_runner).start()
|
threading.Thread(target=notification_runner).start()
|
||||||
|
|
||||||
# Check for new release version, but not when running in test/build
|
# Check for new release version, but not when running in test/build or pytest
|
||||||
if not os.getenv("GITHUB_REF", False):
|
if not os.getenv("GITHUB_REF", False) and not config.get('disable_checkver') == True:
|
||||||
threading.Thread(target=check_for_new_version).start()
|
threading.Thread(target=check_for_new_version).start()
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -185,9 +185,6 @@ class perform_site_check():
|
|||||||
elif is_source:
|
elif is_source:
|
||||||
stripped_text_from_html = html_content
|
stripped_text_from_html = html_content
|
||||||
|
|
||||||
# Re #340 - return the content before the 'ignore text' was applied
|
|
||||||
text_content_before_ignored_filter = stripped_text_from_html.encode('utf-8')
|
|
||||||
|
|
||||||
# Re #340 - return the content before the 'ignore text' was applied
|
# Re #340 - return the content before the 'ignore text' was applied
|
||||||
text_content_before_ignored_filter = stripped_text_from_html.encode('utf-8')
|
text_content_before_ignored_filter = stripped_text_from_html.encode('utf-8')
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,12 @@ class model(dict):
|
|||||||
def save_history_text(self, contents, timestamp):
|
def save_history_text(self, contents, timestamp):
|
||||||
|
|
||||||
self.ensure_data_dir_exists()
|
self.ensure_data_dir_exists()
|
||||||
|
|
||||||
|
# Small hack so that we sleep just enough to allow 1 second between history snapshots
|
||||||
|
# this is because history.txt indexes/keys snapshots by epoch seconds and we dont want dupe keys
|
||||||
|
if self.__newest_history_key and int(timestamp) == int(self.__newest_history_key):
|
||||||
|
time.sleep(timestamp - self.__newest_history_key)
|
||||||
|
|
||||||
snapshot_fname = "{}.txt".format(str(uuid.uuid4()))
|
snapshot_fname = "{}.txt".format(str(uuid.uuid4()))
|
||||||
|
|
||||||
# in /diff/ and /preview/ we are going to assume for now that it's UTF-8 when reading
|
# in /diff/ and /preview/ we are going to assume for now that it's UTF-8 when reading
|
||||||
|
|||||||
@@ -24,14 +24,6 @@ echo "RUNNING WITH BASE_URL SET"
|
|||||||
export BASE_URL="https://really-unique-domain.io"
|
export BASE_URL="https://really-unique-domain.io"
|
||||||
pytest tests/test_notification.py
|
pytest tests/test_notification.py
|
||||||
|
|
||||||
|
|
||||||
## JQ + JSON: filter test
|
|
||||||
# jq is not available on windows and we should just test it when the package is installed
|
|
||||||
# this will re-test with jq support
|
|
||||||
pip3 install jq~=1.3
|
|
||||||
pytest tests/test_jsonpath_jq_selector.py
|
|
||||||
|
|
||||||
|
|
||||||
# Now for the selenium and playwright/browserless fetchers
|
# Now for the selenium and playwright/browserless fetchers
|
||||||
# Note - this is not UI functional tests - just checking that each one can fetch the content
|
# Note - this is not UI functional tests - just checking that each one can fetch the content
|
||||||
|
|
||||||
@@ -46,6 +38,10 @@ unset WEBDRIVER_URL
|
|||||||
docker kill $$-test_selenium
|
docker kill $$-test_selenium
|
||||||
|
|
||||||
echo "TESTING WEBDRIVER FETCH > PLAYWRIGHT/BROWSERLESS..."
|
echo "TESTING WEBDRIVER FETCH > PLAYWRIGHT/BROWSERLESS..."
|
||||||
|
# Not all platforms support playwright (not ARM/rPI), so it's not packaged in requirements.txt
|
||||||
|
PLAYWRIGHT_VERSION=$(grep -i -E "RUN pip install.+" "$SCRIPT_DIR/../Dockerfile" | grep --only-matching -i -E "playwright[=><~+]+[0-9\.]+")
|
||||||
|
echo "using $PLAYWRIGHT_VERSION"
|
||||||
|
pip3 install "$PLAYWRIGHT_VERSION"
|
||||||
docker run -d --name $$-test_browserless -e "DEFAULT_LAUNCH_ARGS=[\"--window-size=1920,1080\"]" --rm -p 3000:3000 --shm-size="2g" browserless/chrome:1.53-chrome-stable
|
docker run -d --name $$-test_browserless -e "DEFAULT_LAUNCH_ARGS=[\"--window-size=1920,1080\"]" --rm -p 3000:3000 --shm-size="2g" browserless/chrome:1.53-chrome-stable
|
||||||
# takes a while to spin up
|
# takes a while to spin up
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class ChangeDetectionStore:
|
|||||||
# For when we edit, we should write to disk
|
# For when we edit, we should write to disk
|
||||||
needs_write_urgent = False
|
needs_write_urgent = False
|
||||||
|
|
||||||
|
__version_check = True
|
||||||
|
|
||||||
def __init__(self, datastore_path="/datastore", include_default_watches=True, version_tag="0.0.0"):
|
def __init__(self, datastore_path="/datastore", include_default_watches=True, version_tag="0.0.0"):
|
||||||
# Should only be active for docker
|
# Should only be active for docker
|
||||||
# logging.basicConfig(filename='/dev/stdout', level=logging.INFO)
|
# logging.basicConfig(filename='/dev/stdout', level=logging.INFO)
|
||||||
@@ -37,7 +39,6 @@ class ChangeDetectionStore:
|
|||||||
self.proxy_list = None
|
self.proxy_list = None
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.stop_thread = False
|
self.stop_thread = False
|
||||||
|
|
||||||
# Base definition for all watchers
|
# Base definition for all watchers
|
||||||
# deepcopy part of #569 - not sure why its needed exactly
|
# deepcopy part of #569 - not sure why its needed exactly
|
||||||
self.generic_definition = deepcopy(Watch.model(datastore_path = datastore_path, default={}))
|
self.generic_definition = deepcopy(Watch.model(datastore_path = datastore_path, default={}))
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def app(request):
|
|||||||
|
|
||||||
cleanup(datastore_path)
|
cleanup(datastore_path)
|
||||||
|
|
||||||
app_config = {'datastore_path': datastore_path}
|
app_config = {'datastore_path': datastore_path, 'disable_checkver' : True}
|
||||||
cleanup(app_config['datastore_path'])
|
cleanup(app_config['datastore_path'])
|
||||||
datastore = store.ChangeDetectionStore(datastore_path=app_config['datastore_path'], include_default_watches=False)
|
datastore = store.ChangeDetectionStore(datastore_path=app_config['datastore_path'], include_default_watches=False)
|
||||||
app = changedetection_app(app_config, datastore)
|
app = changedetection_app(app_config, datastore)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
from .util import set_original_response, set_modified_response, live_server_setup
|
from .util import set_original_response, set_modified_response, live_server_setup, wait_for_all_checks
|
||||||
|
|
||||||
sleep_time_for_fetch_thread = 3
|
sleep_time_for_fetch_thread = 3
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ def test_check_basic_change_detection_functionality(client, live_server):
|
|||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
|
|
||||||
# Give the thread time to pick it up
|
# Give the thread time to pick it up
|
||||||
time.sleep(sleep_time_for_fetch_thread)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
# It should report nothing found (no new 'unviewed' class)
|
# It should report nothing found (no new 'unviewed' class)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
@@ -69,7 +69,7 @@ def test_check_basic_change_detection_functionality(client, live_server):
|
|||||||
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
assert b'1 watches are queued for rechecking.' in res.data
|
assert b'1 watches are queued for rechecking.' in res.data
|
||||||
|
|
||||||
time.sleep(sleep_time_for_fetch_thread)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
# Now something should be ready, indicated by having a 'unviewed' class
|
# Now something should be ready, indicated by having a 'unviewed' class
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
@@ -98,14 +98,14 @@ def test_check_basic_change_detection_functionality(client, live_server):
|
|||||||
assert b'which has this one new line' in res.data
|
assert b'which has this one new line' in res.data
|
||||||
assert b'Which is across multiple lines' not in res.data
|
assert b'Which is across multiple lines' not in res.data
|
||||||
|
|
||||||
time.sleep(2)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
# Do this a few times.. ensures we dont accidently set the status
|
# Do this a few times.. ensures we dont accidently set the status
|
||||||
for n in range(2):
|
for n in range(2):
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
|
|
||||||
# Give the thread time to pick it up
|
# Give the thread time to pick it up
|
||||||
time.sleep(sleep_time_for_fetch_thread)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
# It should report nothing found (no new 'unviewed' class)
|
# It should report nothing found (no new 'unviewed' class)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
@@ -125,7 +125,7 @@ def test_check_basic_change_detection_functionality(client, live_server):
|
|||||||
)
|
)
|
||||||
|
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
time.sleep(sleep_time_for_fetch_thread)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
assert b'unviewed' in res.data
|
assert b'unviewed' in res.data
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ def extract_UUID_from_client(client):
|
|||||||
def wait_for_all_checks(client):
|
def wait_for_all_checks(client):
|
||||||
# Loop waiting until done..
|
# Loop waiting until done..
|
||||||
attempt=0
|
attempt=0
|
||||||
|
time.sleep(0.1)
|
||||||
while attempt < 60:
|
while attempt < 60:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ werkzeug~=2.0.0
|
|||||||
jinja2~=3.1
|
jinja2~=3.1
|
||||||
jinja2-time
|
jinja2-time
|
||||||
|
|
||||||
playwright~=1.26; python_version >= "3.8" and "arm" not in platform_machine and "aarch" not in platform_machine
|
# https://peps.python.org/pep-0508/#environment-markers
|
||||||
|
# https://github.com/dgtlmoon/changedetection.io/pull/1009
|
||||||
|
jq~=1.3 ;python_version >= "3.8" and sys_platform == "linux"
|
||||||
|
|
||||||
|
# playwright is installed at Dockerfile build time because it's not available on all platforms
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user