- {% if watch_needs_selenium_or_playwright %}
- {% if system_has_playwright_configured %}
-
- The Visual Selector tool lets you select the text elements that will be used for the change detection. It automatically fills-in the filters in the "CSS/JSONPath/JQ/XPath Filters" box of the Filters & Triggers tab. Use Shift+Click to select multiple items.
-
+ {% if capabilities.supports_screenshots and capabilities.supports_xpath_element_data %}
+ {% if visual_selector_data_ready %}
+
+ The Visual Selector tool lets you select the text elements that will be used for the change detection. It automatically fills-in the filters in the "CSS/JSONPath/JQ/XPath Filters" box of the Filters & Triggers tab. Use Shift+Click to select multiple items.
+
-
-
-
-
-
-
![]()
-
-
-
Currently: Loading...
- {% else %}
- {# The watch needed chrome but system says that playwright is not ready #}
- {{ playwright_warning() }}
- {% endif %}
- {% if system_has_webdriver_configured %}
-
Selenium/Webdriver cant be used here because it wont fetch screenshots reliably.
- {% endif %}
+
+
+
+
+
+
![]()
+
+
+
Currently: Loading...
+ {% else %}
+
Visual Selector data is not ready, watch needs to be checked atleast once.
+ {% endif %}
{% else %}
{# "This functionality needs chrome.." #}
{{ only_playwright_type_watches_warning() }}
diff --git a/changedetectionio/blueprint/ui/views.py b/changedetectionio/blueprint/ui/views.py
index 27544cac..87503629 100644
--- a/changedetectionio/blueprint/ui/views.py
+++ b/changedetectionio/blueprint/ui/views.py
@@ -57,22 +57,26 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
except Exception as e:
content.append({'line': f"File doesnt exist or unable to read timestamp {timestamp}", 'classes': ''})
+ from changedetectionio.pluggy_interface import get_fetcher_capabilities
+ capabilities = get_fetcher_capabilities(watch, datastore)
+
output = render_template("preview.html",
+ capabilities=capabilities,
content=content,
+ current_diff_url=watch['url'],
current_version=timestamp,
- history_n=watch.history_n,
extra_stylesheets=extra_stylesheets,
extra_title=f" - Diff - {watch.label} @ {timestamp}",
- triggered_line_numbers=triggered_line_numbers,
- current_diff_url=watch['url'],
- screenshot=watch.get_screenshot(),
- watch=watch,
- uuid=uuid,
+ history_n=watch.history_n,
is_html_webdriver=is_html_webdriver,
last_error=watch['last_error'],
- last_error_text=watch.get_error_text(),
last_error_screenshot=watch.get_error_snapshot(),
- versions=versions
+ last_error_text=watch.get_error_text(),
+ screenshot=watch.get_screenshot(),
+ triggered_line_numbers=triggered_line_numbers,
+ uuid=uuid,
+ versions=versions,
+ watch=watch,
)
return output
@@ -174,29 +178,31 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
password_enabled_and_share_is_off = not datastore.data['settings']['application'].get('shared_diff_access')
datastore.set_last_viewed(uuid, time.time())
-
+ from changedetectionio.pluggy_interface import get_fetcher_capabilities
+ capabilities = get_fetcher_capabilities(watch, datastore)
return render_template("diff.html",
- current_diff_url=watch['url'],
- from_version=str(from_version),
- to_version=str(to_version),
- extra_stylesheets=extra_stylesheets,
- extra_title=f" - Diff - {watch.label}",
- extract_form=extract_form,
- is_html_webdriver=is_html_webdriver,
- last_error=watch['last_error'],
- last_error_screenshot=watch.get_error_snapshot(),
- last_error_text=watch.get_error_text(),
- left_sticky=True,
- newest=to_version_file_contents,
- newest_version_timestamp=dates[-1],
- password_enabled_and_share_is_off=password_enabled_and_share_is_off,
- from_version_file_contents=from_version_file_contents,
- to_version_file_contents=to_version_file_contents,
- screenshot=screenshot_url,
- uuid=uuid,
- versions=dates, # All except current/last
- watch_a=watch
- )
+ capabilities=capabilities,
+ current_diff_url=watch['url'],
+ extra_stylesheets=extra_stylesheets,
+ extra_title=f" - Diff - {watch.label}",
+ extract_form=extract_form,
+ from_version=str(from_version),
+ from_version_file_contents=from_version_file_contents,
+ is_html_webdriver=is_html_webdriver,
+ last_error=watch['last_error'],
+ last_error_screenshot=watch.get_error_snapshot(),
+ last_error_text=watch.get_error_text(),
+ left_sticky=True,
+ newest=to_version_file_contents,
+ newest_version_timestamp=dates[-1],
+ password_enabled_and_share_is_off=password_enabled_and_share_is_off,
+ screenshot=screenshot_url,
+ to_version=str(to_version),
+ to_version_file_contents=to_version_file_contents,
+ uuid=uuid,
+ versions=dates, # All except current/last
+ watch_a=watch
+ )
@views_blueprint.route("/diff/
", methods=['GET'])
@login_optionally_required
diff --git a/changedetectionio/content_fetchers/base.py b/changedetectionio/content_fetchers/base.py
index abe1ce2a..247a9965 100644
--- a/changedetectionio/content_fetchers/base.py
+++ b/changedetectionio/content_fetchers/base.py
@@ -64,6 +64,12 @@ class Fetcher():
# Time ONTOP of the system defined env minimum time
render_extract_delay = 0
+ # Fetcher capability flags - subclasses should override these
+ # These indicate what features the fetcher supports
+ supports_browser_steps = False # Can execute browser automation steps
+ supports_screenshots = False # Can capture page screenshots
+ supports_xpath_element_data = False # Can extract xpath element positions/data for visual selector
+
@classmethod
def get_status_icon_data(cls):
"""Return data for status icon to display in the watch overview.
diff --git a/changedetectionio/content_fetchers/playwright.py b/changedetectionio/content_fetchers/playwright.py
index ed82139c..51231f04 100644
--- a/changedetectionio/content_fetchers/playwright.py
+++ b/changedetectionio/content_fetchers/playwright.py
@@ -89,6 +89,11 @@ class fetcher(Fetcher):
proxy = None
+ # Capability flags
+ supports_browser_steps = True
+ supports_screenshots = True
+ supports_xpath_element_data = True
+
@classmethod
def get_status_icon_data(cls):
"""Return Chrome browser icon data for Playwright fetcher."""
diff --git a/changedetectionio/content_fetchers/puppeteer.py b/changedetectionio/content_fetchers/puppeteer.py
index 04eec02b..5d0c1047 100644
--- a/changedetectionio/content_fetchers/puppeteer.py
+++ b/changedetectionio/content_fetchers/puppeteer.py
@@ -98,6 +98,11 @@ class fetcher(Fetcher):
proxy = None
+ # Capability flags
+ supports_browser_steps = True
+ supports_screenshots = True
+ supports_xpath_element_data = True
+
@classmethod
def get_status_icon_data(cls):
"""Return Chrome browser icon data for Puppeteer fetcher."""
diff --git a/changedetectionio/content_fetchers/webdriver_selenium.py b/changedetectionio/content_fetchers/webdriver_selenium.py
index 50bee6a6..28def8f9 100644
--- a/changedetectionio/content_fetchers/webdriver_selenium.py
+++ b/changedetectionio/content_fetchers/webdriver_selenium.py
@@ -14,6 +14,11 @@ class fetcher(Fetcher):
proxy = None
proxy_url = None
+ # Capability flags
+ supports_browser_steps = True
+ supports_screenshots = True
+ supports_xpath_element_data = True
+
@classmethod
def get_status_icon_data(cls):
"""Return Chrome browser icon data for WebDriver fetcher."""
diff --git a/changedetectionio/pluggy_interface.py b/changedetectionio/pluggy_interface.py
index 223f5f09..88a7ffbe 100644
--- a/changedetectionio/pluggy_interface.py
+++ b/changedetectionio/pluggy_interface.py
@@ -229,4 +229,56 @@ def get_itemprop_availability_from_plugin(content, fetcher_name, fetcher_instanc
if result.get('price') is not None or result.get('availability'):
return result
- return None
\ No newline at end of file
+ return None
+
+
+def get_fetcher_capabilities(watch, datastore):
+ """Get capability flags for a watch's fetcher.
+
+ Args:
+ watch: The watch object/dict
+ datastore: The datastore to resolve 'system' fetcher
+
+ Returns:
+ dict: Dictionary with capability flags:
+ {
+ 'supports_browser_steps': bool,
+ 'supports_screenshots': bool,
+ 'supports_xpath_element_data': bool
+ }
+ """
+ # Get the fetcher name from watch
+ fetcher_name = watch.get('fetch_backend', 'system')
+
+ # Resolve 'system' to actual fetcher
+ if fetcher_name == 'system':
+ fetcher_name = datastore.data['settings']['application'].get('fetch_backend', 'html_requests')
+
+ # Get the fetcher class
+ from changedetectionio import content_fetchers
+
+ # Try to get from built-in fetchers first
+ if hasattr(content_fetchers, fetcher_name):
+ fetcher_class = getattr(content_fetchers, fetcher_name)
+ return {
+ 'supports_browser_steps': getattr(fetcher_class, 'supports_browser_steps', False),
+ 'supports_screenshots': getattr(fetcher_class, 'supports_screenshots', False),
+ 'supports_xpath_element_data': getattr(fetcher_class, 'supports_xpath_element_data', False)
+ }
+
+ # Try to get from plugin-provided fetchers
+ fetchers = collect_content_fetchers()
+ for name, fetcher_class in fetchers:
+ if name == fetcher_name:
+ return {
+ 'supports_browser_steps': getattr(fetcher_class, 'supports_browser_steps', False),
+ 'supports_screenshots': getattr(fetcher_class, 'supports_screenshots', False),
+ 'supports_xpath_element_data': getattr(fetcher_class, 'supports_xpath_element_data', False)
+ }
+
+ # Default: no capabilities
+ return {
+ 'supports_browser_steps': False,
+ 'supports_screenshots': False,
+ 'supports_xpath_element_data': False
+ }
\ No newline at end of file
diff --git a/changedetectionio/templates/diff.html b/changedetectionio/templates/diff.html
index f7fdf868..603720e4 100644
--- a/changedetectionio/templates/diff.html
+++ b/changedetectionio/templates/diff.html
@@ -112,7 +112,7 @@
For now, Differences are performed on text, not graphically, only the latest screenshot is available.
- {% if is_html_webdriver %}
+ {% if capabilities.get('supports_screenshots') %}
{% if screenshot %}
{{watch_a.snapshot_screenshot_ctime|format_timestamp_timeago}}
@@ -120,7 +120,7 @@
No screenshot available just yet! Try rechecking the page.
{% endif %}
{% else %}
- Screenshot requires Playwright/WebDriver enabled
+ Screenshot requires a Content Fetcher ( Chrome, Zyte etc ) that supports screenshots.
{% endif %}