Compare commits

...

14 Commits

Author SHA1 Message Date
dgtlmoon
74799cd840 its OK to defer here 2025-02-17 21:40:16 +01:00
dgtlmoon
467f055b67 Use JS for switching tabs because sometimes the DOM wasnt ready when the CSS tried to fire &:target 2025-02-17 21:37:22 +01:00
dgtlmoon
82211eef82 Update settings.html
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2025-02-11 11:15:13 +01:00
dgtlmoon
5d9380609c Browser Steps - Increasing timeout for actions and unifying timeout values
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2025-02-10 10:56:44 +01:00
dgtlmoon
a8b3918fca Browser Steps - Fixing 'Uncheck checkbox' #2958 2025-02-10 10:49:40 +01:00
dgtlmoon
e83fb37fb6 UI - "Browser Steps" tab should be always available with helpful info (evenwhen playwright is not configured) (#2955)
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2025-02-10 00:36:35 +01:00
dgtlmoon
6b99afe0f7 Adding browser_steps JSON Schema rule for API updates (#2957) 2025-02-10 00:35:39 +01:00
dgtlmoon
09ebc6ec63 UI - Fix mute/unmute alt/title label alt/title text in watch overview (#2951)
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2025-02-08 18:01:26 +01:00
dgtlmoon
6b1065502e 0.49.1
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2025-02-08 10:14:19 +01:00
vin86
d4c470984a Update stock-not-in-stock.js - Italian (#2948)
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2025-02-08 00:23:20 +01:00
dgtlmoon
55da48f719 Re #2945 - Handle/Strip UTF-8 ByteOrderMark in JSON strings correctly (fixes "Exception: No parsable JSON found in this document" error) (#2947)
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2025-02-07 22:19:23 +01:00
RoboMagus
dbd4adf23a Add major and minor tags for Docker release workflow (#2938)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / test-container-build (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2025-02-01 10:52:04 +01:00
dgtlmoon
b1e700b3ff Adding jinja2/browsersteps test (#2915)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2025-01-28 18:14:49 +01:00
Iftekhar Alam Fuad
1c61b5a623 Header handling - Fix header parsing to split on the first colon only (headers where the value contained :// type may have been broken) (#2929)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-01-26 00:08:09 +01:00
31 changed files with 245 additions and 113 deletions

View File

@@ -103,6 +103,19 @@ jobs:
# provenance: false # provenance: false
# A new tagged release is required, which builds :tag and :latest # A new tagged release is required, which builds :tag and :latest
- name: Docker meta :tag
if: github.event_name == 'release' && startsWith(github.event.release.tag_name, '0.')
uses: docker/metadata-action@v5
id: meta
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io
ghcr.io/dgtlmoon/changedetection.io
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push :tag - name: Build and push :tag
id: docker_build_tag_release id: docker_build_tag_release
if: github.event_name == 'release' && startsWith(github.event.release.tag_name, '0.') if: github.event_name == 'release' && startsWith(github.event.release.tag_name, '0.')
@@ -111,11 +124,7 @@ jobs:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile
push: true push: true
tags: | tags: ${{ steps.meta.outputs.tags }}
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io:${{ github.event.release.tag_name }}
ghcr.io/dgtlmoon/changedetection.io:${{ github.event.release.tag_name }}
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io:latest
ghcr.io/dgtlmoon/changedetection.io:latest
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/arm64/v8 platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/arm64/v8
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max

View File

@@ -2,7 +2,7 @@
# Read more https://github.com/dgtlmoon/changedetection.io/wiki # Read more https://github.com/dgtlmoon/changedetection.io/wiki
__version__ = '0.49.0' __version__ = '0.49.1'
from changedetectionio.strtobool import strtobool from changedetectionio.strtobool import strtobool
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError

View File

@@ -112,6 +112,35 @@ def build_watch_json_schema(d):
schema['properties']['time_between_check'] = build_time_between_check_json_schema() schema['properties']['time_between_check'] = build_time_between_check_json_schema()
schema['properties']['browser_steps'] = {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"operation": {
"type": ["string", "null"],
"maxLength": 5000 # Allows null and any string up to 5000 chars (including "")
},
"selector": {
"type": ["string", "null"],
"maxLength": 5000
},
"optional_value": {
"type": ["string", "null"],
"maxLength": 5000
}
},
"required": ["operation", "selector", "optional_value"],
"additionalProperties": False # No extra keys allowed
}
},
{"type": "null"}, # Allows null for `browser_steps`
{"type": "array", "maxItems": 0} # Allows empty array []
]
}
# headers ? # headers ?
return schema return schema

View File

@@ -52,6 +52,8 @@ class steppable_browser_interface():
page = None page = None
start_url = None start_url = None
action_timeout = 10 * 1000
def __init__(self, start_url): def __init__(self, start_url):
self.start_url = start_url self.start_url = start_url
@@ -102,7 +104,7 @@ class steppable_browser_interface():
return return
elem = self.page.get_by_text(value) elem = self.page.get_by_text(value)
if elem.count(): if elem.count():
elem.first.click(delay=randint(200, 500), timeout=3000) elem.first.click(delay=randint(200, 500), timeout=self.action_timeout)
def action_click_element_containing_text_if_exists(self, selector=None, value=''): def action_click_element_containing_text_if_exists(self, selector=None, value=''):
logger.debug("Clicking element containing text if exists") logger.debug("Clicking element containing text if exists")
@@ -111,7 +113,7 @@ class steppable_browser_interface():
elem = self.page.get_by_text(value) elem = self.page.get_by_text(value)
logger.debug(f"Clicking element containing text - {elem.count()} elements found") logger.debug(f"Clicking element containing text - {elem.count()} elements found")
if elem.count(): if elem.count():
elem.first.click(delay=randint(200, 500), timeout=3000) elem.first.click(delay=randint(200, 500), timeout=self.action_timeout)
else: else:
return return
@@ -119,7 +121,7 @@ class steppable_browser_interface():
if not len(selector.strip()): if not len(selector.strip()):
return return
self.page.fill(selector, value, timeout=10 * 1000) self.page.fill(selector, value, timeout=self.action_timeout)
def action_execute_js(self, selector, value): def action_execute_js(self, selector, value):
response = self.page.evaluate(value) response = self.page.evaluate(value)
@@ -130,7 +132,7 @@ class steppable_browser_interface():
if not len(selector.strip()): if not len(selector.strip()):
return return
self.page.click(selector=selector, timeout=30 * 1000, delay=randint(200, 500)) self.page.click(selector=selector, timeout=self.action_timeout + 20 * 1000, delay=randint(200, 500))
def action_click_element_if_exists(self, selector, value): def action_click_element_if_exists(self, selector, value):
import playwright._impl._errors as _api_types import playwright._impl._errors as _api_types
@@ -138,7 +140,7 @@ class steppable_browser_interface():
if not len(selector.strip()): if not len(selector.strip()):
return return
try: try:
self.page.click(selector, timeout=10 * 1000, delay=randint(200, 500)) self.page.click(selector, timeout=self.action_timeout, delay=randint(200, 500))
except _api_types.TimeoutError as e: except _api_types.TimeoutError as e:
return return
except _api_types.Error as e: except _api_types.Error as e:
@@ -185,10 +187,10 @@ class steppable_browser_interface():
self.page.keyboard.press("PageDown", delay=randint(200, 500)) self.page.keyboard.press("PageDown", delay=randint(200, 500))
def action_check_checkbox(self, selector, value): def action_check_checkbox(self, selector, value):
self.page.locator(selector).check(timeout=1000) self.page.locator(selector).check(timeout=self.action_timeout)
def action_uncheck_checkbox(self, selector, value): def action_uncheck_checkbox(self, selector, value):
self.page.locator(selector, timeout=1000).uncheck(timeout=1000) self.page.locator(selector).uncheck(timeout=self.action_timeout)
# Responsible for maintaining a live 'context' with the chrome CDP # Responsible for maintaining a live 'context' with the chrome CDP

View File

@@ -52,6 +52,7 @@ function isItemInStock() {
'niet leverbaar', 'niet leverbaar',
'niet op voorraad', 'niet op voorraad',
'no disponible', 'no disponible',
'non disponibile',
'no longer in stock', 'no longer in stock',
'no tickets available', 'no tickets available',
'not available', 'not available',

View File

@@ -1,5 +1,6 @@
from typing import List from loguru import logger
from lxml import etree from lxml import etree
from typing import List
import json import json
import re import re
@@ -298,8 +299,10 @@ def extract_json_as_string(content, json_filter, ensure_is_ldjson_info_type=None
# https://github.com/dgtlmoon/changedetection.io/pull/2041#issuecomment-1848397161w # https://github.com/dgtlmoon/changedetection.io/pull/2041#issuecomment-1848397161w
# Try to parse/filter out the JSON, if we get some parser error, then maybe it's embedded within HTML tags # Try to parse/filter out the JSON, if we get some parser error, then maybe it's embedded within HTML tags
try: try:
stripped_text_from_html = _parse_json(json.loads(content), json_filter) # .lstrip("\ufeff") strings ByteOrderMark from UTF8 and still lets the UTF work
except json.JSONDecodeError: stripped_text_from_html = _parse_json(json.loads(content.lstrip("\ufeff") ), json_filter)
except json.JSONDecodeError as e:
logger.warning(str(e))
# Foreach <script json></script> blob.. just return the first that matches json_filter # Foreach <script json></script> blob.. just return the first that matches json_filter
# As a last resort, try to parse the whole <body> # As a last resort, try to parse the whole <body>

View File

@@ -69,7 +69,7 @@ def parse_headers_from_text_file(filepath):
for l in f.readlines(): for l in f.readlines():
l = l.strip() l = l.strip()
if not l.startswith('#') and ':' in l: if not l.startswith('#') and ':' in l:
(k, v) = l.split(':') (k, v) = l.split(':', 1) # Split only on the first colon
headers[k.strip()] = v.strip() headers[k.strip()] = v.strip()
return headers return headers

View File

@@ -1,48 +1,66 @@
// Rewrite this is a plugin.. is all this JS really 'worth it?' (function ($) {
$.fn.hashTabs = function (options) {
var settings = $.extend({
tabContainer: ".tabs ul",
tabSelector: "li a",
tabContent: ".tab-pane-inner",
activeClass: "active",
errorClass: ".messages .error",
bodyClassToggle: "full-width"
}, options);
window.addEventListener('hashchange', function () { var $tabs = $(settings.tabContainer).find(settings.tabSelector);
var tabs = document.getElementsByClassName('active');
while (tabs[0]) {
tabs[0].classList.remove('active');
document.body.classList.remove('full-width');
}
set_active_tab();
}, false);
var has_errors = document.querySelectorAll(".messages .error"); function setActiveTab() {
if (!has_errors.length) { var hash = window.location.hash;
if (document.location.hash == "") { var $activeTab = $tabs.filter("[href='" + hash + "']");
location.replace(document.querySelector(".tabs ul li:first-child a").hash);
} else {
set_active_tab();
}
} else {
focus_error_tab();
}
function set_active_tab() { // Remove active class from all tabs
document.body.classList.remove('full-width'); $(settings.tabContainer).find("li").removeClass(settings.activeClass);
var tab = document.querySelectorAll("a[href='" + location.hash + "']");
if (tab.length) {
tab[0].parentElement.className = "active";
}
} // Add active class to selected tab
if ($activeTab.length) {
$activeTab.parent().addClass(settings.activeClass);
}
function focus_error_tab() { // Show the correct content
// time to use jquery or vuejs really, $(settings.tabContent).hide();
// activate the tab with the error if (hash) {
var tabs = document.querySelectorAll('.tabs li a'), i; $(hash).show();
for (i = 0; i < tabs.length; ++i) { }
var tab_name = tabs[i].hash.replace('#', '');
var pane_errors = document.querySelectorAll('#' + tab_name + ' .error')
if (pane_errors.length) {
document.location.hash = '#' + tab_name;
return true;
} }
}
return false; function focusErrorTab() {
} $tabs.each(function () {
var tabName = this.hash.replace("#", "");
if ($("#" + tabName).find(settings.errorClass).length) {
window.location.hash = "#" + tabName;
return false; // Stop loop on first error tab
}
});
}
function initializeTabs() {
if ($(settings.errorClass).length) {
focusErrorTab();
} else if (!window.location.hash) {
window.location.replace($tabs.first().attr("href"));
} else {
setActiveTab();
}
}
// Listen for hash changes
$(window).on("hashchange", setActiveTab);
// Initialize on page load
initializeTabs();
return this; // Enable jQuery chaining
};
})(jQuery);
$(document).ready(function () {
$(".tabs").hashTabs();
});

View File

@@ -945,15 +945,7 @@ $form-edge-padding: 20px;
} }
.tab-pane-inner { .tab-pane-inner {
display: none;
&:not(:target) {
display: none;
}
&:target {
display: block;
}
// doesnt need padding because theres another row of buttons/activity // doesnt need padding because theres another row of buttons/activity
padding: 0px; padding: 0px;
} }

View File

@@ -1159,11 +1159,8 @@ textarea::placeholder {
border-radius: 5px; } border-radius: 5px; }
.tab-pane-inner { .tab-pane-inner {
display: none;
padding: 0px; } padding: 0px; }
.tab-pane-inner:not(:target) {
display: none; }
.tab-pane-inner:target {
display: block; }
.beta-logo { .beta-logo {
height: 50px; height: 50px;

View File

@@ -45,9 +45,8 @@
{% if extra_tab_content %} {% if extra_tab_content %}
<li class="tab"><a href="#extras_tab">{{ extra_tab_content }}</a></li> <li class="tab"><a href="#extras_tab">{{ extra_tab_content }}</a></li>
{% endif %} {% endif %}
{% if playwright_enabled %}
<li class="tab"><a id="browsersteps-tab" href="#browser-steps">Browser Steps</a></li> <li class="tab"><a id="browsersteps-tab" href="#browser-steps">Browser Steps</a></li>
{% endif %} <!-- should goto extra forms? -->
{% if watch['processor'] == 'text_json_diff' %} {% if watch['processor'] == 'text_json_diff' %}
<li class="tab"><a id="visualselector-tab" href="#visualselector">Visual Filter Selector</a></li> <li class="tab"><a id="visualselector-tab" href="#visualselector">Visual Filter Selector</a></li>
<li class="tab" id="filters-and-triggers-tab"><a href="#filters-and-triggers">Filters &amp; Triggers</a></li> <li class="tab" id="filters-and-triggers-tab"><a href="#filters-and-triggers">Filters &amp; Triggers</a></li>
@@ -199,8 +198,9 @@ Math: {{ 1 + 1 }}") }}
</div> </div>
</fieldset> </fieldset>
</div> </div>
{% if playwright_enabled %}
<div class="tab-pane-inner" id="browser-steps"> <div class="tab-pane-inner" id="browser-steps">
{% if playwright_enabled %}
<img class="beta-logo" src="{{url_for('static_content', group='images', filename='beta-logo.png')}}" alt="New beta functionality"> <img class="beta-logo" src="{{url_for('static_content', group='images', filename='beta-logo.png')}}" alt="New beta functionality">
<fieldset> <fieldset>
<div class="pure-control-group"> <div class="pure-control-group">
@@ -240,8 +240,16 @@ Math: {{ 1 + 1 }}") }}
</div> </div>
</div> </div>
</fieldset> </fieldset>
{% else %}
<span class="pure-form-message-inline">
<p>Sorry, this functionality only works with Playwright/Chrome enabled watches.</p>
<p>Enable the Playwright Chrome fetcher, or alternatively try our <a href="https://lemonade.changedetection.io/start">very affordable subscription based service</a>.</p>
<p>This is because Selenium/WebDriver can not extract full page screenshots reliably.</p>
<p>You may need to <a href="https://github.com/dgtlmoon/changedetection.io/blob/09ebc6ec6338545bdd694dc6eee57f2e9d2b8075/docker-compose.yml#L31">Enable playwright environment variable</a> and uncomment the <strong>sockpuppetbrowser</strong> from the docker-compose.yml file.</p>
</span>
{% endif %}
</div> </div>
{% endif %}
<div class="tab-pane-inner" id="notifications"> <div class="tab-pane-inner" id="notifications">
<fieldset> <fieldset>
@@ -493,6 +501,7 @@ keyword") }}
<p>Sorry, this functionality only works with Playwright/Chrome enabled watches.</p> <p>Sorry, this functionality only works with Playwright/Chrome enabled watches.</p>
<p>Enable the Playwright Chrome fetcher, or alternatively try our <a href="https://lemonade.changedetection.io/start">very affordable subscription based service</a>.</p> <p>Enable the Playwright Chrome fetcher, or alternatively try our <a href="https://lemonade.changedetection.io/start">very affordable subscription based service</a>.</p>
<p>This is because Selenium/WebDriver can not extract full page screenshots reliably.</p> <p>This is because Selenium/WebDriver can not extract full page screenshots reliably.</p>
<p>You may need to <a href="https://github.com/dgtlmoon/changedetection.io/blob/09ebc6ec6338545bdd694dc6eee57f2e9d2b8075/docker-compose.yml#L31">Enable playwright environment variable</a> and uncomment the <strong>sockpuppetbrowser</strong> from the docker-compose.yml file.</p>
</span> </span>
{% endif %} {% endif %}
</div> </div>

View File

@@ -280,9 +280,7 @@ nav
</div> </div>
</div> </div>
<p>
Your proxy provider may need to whitelist our IP of <code>204.15.192.195</code>
</p>
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites. <p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.
<div class="pure-control-group" id="extra-proxies-setting"> <div class="pure-control-group" id="extra-proxies-setting">

View File

@@ -108,7 +108,8 @@
{% else %} {% else %}
<a class="state-on" href="{{url_for('index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='play.svg')}}" alt="UnPause checks" title="UnPause checks" class="icon icon-unpause" ></a> <a class="state-on" href="{{url_for('index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='play.svg')}}" alt="UnPause checks" title="UnPause checks" class="icon icon-unpause" ></a>
{% endif %} {% endif %}
<a class="link-mute state-{{'on' if watch.notification_muted else 'off'}}" href="{{url_for('index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notifications" title="Mute notifications" class="icon icon-mute" ></a> {% set mute_label = 'UnMute notification' if watch.notification_muted else 'Mute notification' %}
<a class="link-mute state-{{'on' if watch.notification_muted else 'off'}}" href="{{url_for('index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="{{ mute_label }}" title="{{ mute_label }}" class="icon icon-mute" ></a>
</td> </td>
<td class="title-col inline">{{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}} <td class="title-col inline">{{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}}
<a class="external" target="_blank" rel="noopener" href="{{ watch.link.replace('source:','') }}"></a> <a class="external" target="_blank" rel="noopener" href="{{ watch.link.replace('source:','') }}"></a>

View File

@@ -34,7 +34,7 @@ def test_execute_custom_js(client, live_server, measure_memory_usage):
assert b"unpaused" in res.data assert b"unpaused" in res.data
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
assert live_server.app.config['DATASTORE'].data['watching'][uuid].history_n >= 1, "Watch history had atleast 1 (everything fetched OK)" assert live_server.app.config['DATASTORE'].data['watching'][uuid].history_n >= 1, "Watch history had atleast 1 (everything fetched OK)"
assert b"This text should be removed" not in res.data assert b"This text should be removed" not in res.data

View File

@@ -48,7 +48,7 @@ def test_noproxy_option(client, live_server, measure_memory_usage):
follow_redirects=True follow_redirects=True
) )
assert b"Watch added in Paused state, saving will unpause" in res.data assert b"Watch added in Paused state, saving will unpause" in res.data
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.get( res = client.get(
url_for("edit_page", uuid=uuid, unpause_on_save=1)) url_for("edit_page", uuid=uuid, unpause_on_save=1))
assert b'No proxy' in res.data assert b'No proxy' in res.data

View File

@@ -81,7 +81,7 @@ def test_socks5(client, live_server, measure_memory_usage):
assert "Awesome, you made it".encode('utf-8') in res.data assert "Awesome, you made it".encode('utf-8') in res.data
# PROXY CHECKER WIDGET CHECK - this needs more checking # PROXY CHECKER WIDGET CHECK - this needs more checking
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.get( res = client.get(
url_for("check_proxies.start_check", uuid=uuid), url_for("check_proxies.start_check", uuid=uuid),

View File

@@ -99,7 +99,7 @@ def test_check_ldjson_price_autodetect(client, live_server, measure_memory_usage
assert b'ldjson-price-track-offer' in res.data assert b'ldjson-price-track-offer' in res.data
# Accept it # Accept it
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
#time.sleep(1) #time.sleep(1)
client.get(url_for('price_data_follower.accept', uuid=uuid, follow_redirects=True)) client.get(url_for('price_data_follower.accept', uuid=uuid, follow_redirects=True))
client.get(url_for("form_watch_checknow"), follow_redirects=True) client.get(url_for("form_watch_checknow"), follow_redirects=True)

View File

@@ -68,7 +68,7 @@ def test_check_basic_change_detection_functionality(client, live_server, measure
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
# Check the 'get latest snapshot works' # Check the 'get latest snapshot works'
res = client.get(url_for("watch_get_latest_html", uuid=uuid)) res = client.get(url_for("watch_get_latest_html", uuid=uuid))

View File

@@ -40,7 +40,7 @@ def test_check_encoding_detection(client, live_server, measure_memory_usage):
# Content type recording worked # Content type recording worked
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
assert live_server.app.config['DATASTORE'].data['watching'][uuid]['content-type'] == "text/html" assert live_server.app.config['DATASTORE'].data['watching'][uuid]['content-type'] == "text/html"
res = client.get( res = client.get(

View File

@@ -51,7 +51,7 @@ def run_filter_test(client, live_server, content_filter):
assert b"1 Imported" in res.data assert b"1 Imported" in res.data
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
assert live_server.app.config['DATASTORE'].data['watching'][uuid]['consecutive_filter_failures'] == 0, "No filter = No filter failure" assert live_server.app.config['DATASTORE'].data['watching'][uuid]['consecutive_filter_failures'] == 0, "No filter = No filter failure"

View File

@@ -288,7 +288,7 @@ def test_clone_tag_on_import(client, live_server, measure_memory_usage):
assert b'test-tag' in res.data assert b'test-tag' in res.data
assert b'another-tag' in res.data assert b'another-tag' in res.data
watch_uuid = extract_UUID_from_client(client) watch_uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.get(url_for("form_clone", uuid=watch_uuid), follow_redirects=True) res = client.get(url_for("form_clone", uuid=watch_uuid), follow_redirects=True)
assert b'Cloned' in res.data assert b'Cloned' in res.data
@@ -315,7 +315,7 @@ def test_clone_tag_on_quickwatchform_add(client, live_server, measure_memory_usa
assert b'test-tag' in res.data assert b'test-tag' in res.data
assert b'another-tag' in res.data assert b'another-tag' in res.data
watch_uuid = extract_UUID_from_client(client) watch_uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.get(url_for("form_clone", uuid=watch_uuid), follow_redirects=True) res = client.get(url_for("form_clone", uuid=watch_uuid), follow_redirects=True)
assert b'Cloned' in res.data assert b'Cloned' in res.data

View File

@@ -36,7 +36,7 @@ def test_ignore(client, live_server, measure_memory_usage):
# Give the thread time to pick it up # Give the thread time to pick it up
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
# use the highlighter endpoint # use the highlighter endpoint
res = client.post( res = client.post(
url_for("highlight_submit_ignore_url", uuid=uuid), url_for("highlight_submit_ignore_url", uuid=uuid),

View File

@@ -514,3 +514,15 @@ def test_check_jq_ext_filter(client, live_server, measure_memory_usage):
def test_check_jqraw_ext_filter(client, live_server, measure_memory_usage): def test_check_jqraw_ext_filter(client, live_server, measure_memory_usage):
if jq_support: if jq_support:
check_json_ext_filter('jq:.[] | select(.status | contains("Sold"))', client, live_server) check_json_ext_filter('jq:.[] | select(.status | contains("Sold"))', client, live_server)
def test_jsonpath_BOM_utf8(client, live_server, measure_memory_usage):
from .. import html_tools
# JSON string with BOM and correct double-quoted keys
json_str = '\ufeff{"name": "José", "emoji": "😊", "language": "中文", "greeting": "Привет"}'
# See that we can find the second <script> one, which is not broken, and matches our filter
text = html_tools.extract_json_as_string(json_str, "json:$.name")
assert text == '"José"'

View File

@@ -29,7 +29,7 @@ def test_content_filter_live_preview(client, live_server, measure_memory_usage):
data={"url": test_url, "tags": ''}, data={"url": test_url, "tags": ''},
follow_redirects=True follow_redirects=True
) )
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.post( res = client.post(
url_for("edit_page", uuid=uuid), url_for("edit_page", uuid=uuid),
data={ data={

View File

@@ -6,7 +6,7 @@ from flask import url_for
from loguru import logger from loguru import logger
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks, \ from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks, \
set_longer_modified_response set_longer_modified_response, get_index
from . util import extract_UUID_from_client from . util import extract_UUID_from_client
import logging import logging
import base64 import base64
@@ -76,7 +76,7 @@ def test_check_notification(client, live_server, measure_memory_usage):
testimage_png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' testimage_png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
datastore = 'test-datastore' datastore = 'test-datastore'
with open(os.path.join(datastore, str(uuid), 'last-screenshot.png'), 'wb') as f: with open(os.path.join(datastore, str(uuid), 'last-screenshot.png'), 'wb') as f:
f.write(base64.b64decode(testimage_png)) f.write(base64.b64decode(testimage_png))
@@ -328,7 +328,7 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server, measure_me
# Check no errors were recorded, because we asked for 204 which is slightly uncommon but is still OK # Check no errors were recorded, because we asked for 204 which is slightly uncommon but is still OK
res = client.get(url_for("index")) res = get_index(client)
assert b'notification-error' not in res.data assert b'notification-error' not in res.data
with open("test-datastore/notification.txt", 'r') as f: with open("test-datastore/notification.txt", 'r') as f:

View File

@@ -373,13 +373,14 @@ def test_headers_textfile_in_request(client, live_server, measure_memory_usage):
wait_for_all_checks(client) wait_for_all_checks(client)
with open('test-datastore/headers-testtag.txt', 'w') as f: with open('test-datastore/headers-testtag.txt', 'w') as f:
f.write("tag-header: test") f.write("tag-header: test\r\nurl-header: http://example.com")
with open('test-datastore/headers.txt', 'w') as f: with open('test-datastore/headers.txt', 'w') as f:
f.write("global-header: nice\r\nnext-global-header: nice") f.write("global-header: nice\r\nnext-global-header: nice\r\nurl-header-global: http://example.com/global")
with open('test-datastore/' + extract_UUID_from_client(client) + '/headers.txt', 'w') as f: uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
f.write("watch-header: nice") with open(f'test-datastore/{uuid}/headers.txt', 'w') as f:
f.write("watch-header: nice\r\nurl-header-watch: http://example.com/watch")
wait_for_all_checks(client) wait_for_all_checks(client)
client.get(url_for("form_watch_checknow"), follow_redirects=True) client.get(url_for("form_watch_checknow"), follow_redirects=True)
@@ -410,6 +411,9 @@ def test_headers_textfile_in_request(client, live_server, measure_memory_usage):
assert b"Xxx:ooo" in res.data assert b"Xxx:ooo" in res.data
assert b"Watch-Header:nice" in res.data assert b"Watch-Header:nice" in res.data
assert b"Tag-Header:test" in res.data assert b"Tag-Header:test" in res.data
assert b"Url-Header:http://example.com" in res.data
assert b"Url-Header-Global:http://example.com/global" in res.data
assert b"Url-Header-Watch:http://example.com/watch" in res.data
# Check the custom UA from system settings page made it through # Check the custom UA from system settings page made it through
if os.getenv('PLAYWRIGHT_DRIVER_URL'): if os.getenv('PLAYWRIGHT_DRIVER_URL'):

View File

@@ -380,7 +380,7 @@ def test_change_with_notification_values(client, live_server):
## Now test the "SEND TEST NOTIFICATION" is working ## Now test the "SEND TEST NOTIFICATION" is working
os.unlink("test-datastore/notification.txt") os.unlink("test-datastore/notification.txt")
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.post(url_for("ajax_callback_send_notification_test", watch_uuid=uuid), data={}, follow_redirects=True) res = client.post(url_for("ajax_callback_send_notification_test", watch_uuid=uuid), data={}, follow_redirects=True)
time.sleep(5) time.sleep(5)
assert os.path.isfile("test-datastore/notification.txt"), "Notification received" assert os.path.isfile("test-datastore/notification.txt"), "Notification received"

View File

@@ -132,7 +132,7 @@ def test_rss_xpath_filtering(client, live_server, measure_memory_usage):
) )
assert b"Watch added in Paused state, saving will unpause" in res.data assert b"Watch added in Paused state, saving will unpause" in res.data
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.post( res = client.post(
url_for("edit_page", uuid=uuid, unpause_on_save=1), url_for("edit_page", uuid=uuid, unpause_on_save=1),
data={ data={

View File

@@ -39,7 +39,7 @@ def test_check_basic_scheduler_functionality(client, live_server, measure_memory
assert b"1 Imported" in res.data assert b"1 Imported" in res.data
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
# Setup all the days of the weeks using XXX as the placeholder for monday/tuesday/etc # Setup all the days of the weeks using XXX as the placeholder for monday/tuesday/etc
@@ -104,7 +104,7 @@ def test_check_basic_global_scheduler_functionality(client, live_server, measure
assert b"1 Imported" in res.data assert b"1 Imported" in res.data
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
# Setup all the days of the weeks using XXX as the placeholder for monday/tuesday/etc # Setup all the days of the weeks using XXX as the placeholder for monday/tuesday/etc

View File

@@ -285,15 +285,43 @@ def live_server_setup(live_server):
<p id="remove">This text should be removed</p> <p id="remove">This text should be removed</p>
<form onsubmit="event.preventDefault();"> <form onsubmit="event.preventDefault();">
<!-- obfuscated text so that we dont accidentally get a false positive due to conversion of the source :) ---> <!-- obfuscated text so that we dont accidentally get a false positive due to conversion of the source :) --->
<button name="test-button" onclick="getElementById('remove').remove();getElementById('some-content').innerHTML = atob('SSBzbWVsbCBKYXZhU2NyaXB0IGJlY2F1c2UgdGhlIGJ1dHRvbiB3YXMgcHJlc3NlZCE=')">Click here</button> <button name="test-button" onclick="
<div id=some-content></div> getElementById('remove').remove();
getElementById('some-content').innerHTML = atob('SSBzbWVsbCBKYXZhU2NyaXB0IGJlY2F1c2UgdGhlIGJ1dHRvbiB3YXMgcHJlc3NlZCE=');
getElementById('reflect-text').innerHTML = getElementById('test-input-text').value;
">Click here</button>
<div id="some-content"></div>
<pre> <pre>
{header_text.lower()} {header_text.lower()}
</pre> </pre>
</body>
<br>
<!-- used for testing that the jinja2 compiled here --->
<input type="text" value="" id="test-input-text" /><br>
<div id="reflect-text">Waiting to reflect text from #test-input-text here</div>
</form>
</body>
</html>""", 200) </html>""", 200)
resp.headers['Content-Type'] = 'text/html' resp.headers['Content-Type'] = 'text/html'
return resp return resp
live_server.start() live_server.start()
def get_index(client):
import inspect
# Get the caller's frame (parent function)
frame = inspect.currentframe()
caller_frame = frame.f_back # Go back to the caller's frame
caller_name = caller_frame.f_code.co_name
caller_line = caller_frame.f_lineno
print(f"Called by: {caller_name}, Line: {caller_line}")
res = client.get(url_for("index"))
with open(f"test-datastore/index-{caller_name}-{caller_line}.html", 'wb') as f:
f.write(res.data)
return res

View File

@@ -2,14 +2,16 @@
import os import os
from flask import url_for from flask import url_for
from ..util import live_server_setup, wait_for_all_checks, extract_UUID_from_client from ..util import live_server_setup, wait_for_all_checks, get_index
def test_setup(client, live_server, measure_memory_usage): def test_setup(client, live_server):
live_server_setup(live_server) live_server_setup(live_server)
# Add a site in paused mode, add an invalid filter, we should still have visual selector data ready # Add a site in paused mode, add an invalid filter, we should still have visual selector data ready
def test_visual_selector_content_ready(client, live_server, measure_memory_usage): def test_visual_selector_content_ready(client, live_server, measure_memory_usage):
live_server.stop()
live_server.start()
import os import os
import json import json
@@ -27,7 +29,7 @@ def test_visual_selector_content_ready(client, live_server, measure_memory_usage
follow_redirects=True follow_redirects=True
) )
assert b"Watch added in Paused state, saving will unpause" in res.data assert b"Watch added in Paused state, saving will unpause" in res.data
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
res = client.post( res = client.post(
url_for("edit_page", uuid=uuid, unpause_on_save=1), url_for("edit_page", uuid=uuid, unpause_on_save=1),
data={ data={
@@ -87,7 +89,9 @@ def test_visual_selector_content_ready(client, live_server, measure_memory_usage
def test_basic_browserstep(client, live_server, measure_memory_usage): def test_basic_browserstep(client, live_server, measure_memory_usage):
#live_server_setup(live_server) live_server.stop()
live_server.start()
assert os.getenv('PLAYWRIGHT_DRIVER_URL'), "Needs PLAYWRIGHT_DRIVER_URL set for this test" assert os.getenv('PLAYWRIGHT_DRIVER_URL'), "Needs PLAYWRIGHT_DRIVER_URL set for this test"
test_url = url_for('test_interactive_html_endpoint', _external=True) test_url = url_for('test_interactive_html_endpoint', _external=True)
@@ -108,9 +112,13 @@ def test_basic_browserstep(client, live_server, measure_memory_usage):
"url": test_url, "url": test_url,
"tags": "", "tags": "",
'fetch_backend': "html_webdriver", 'fetch_backend': "html_webdriver",
'browser_steps-0-operation': 'Click element', 'browser_steps-0-operation': 'Enter text in field',
'browser_steps-0-selector': 'button[name=test-button]', 'browser_steps-0-selector': '#test-input-text',
'browser_steps-0-optional_value': '', # Should get set to the actual text (jinja2 rendered)
'browser_steps-0-optional_value': "Hello-Jinja2-{% now 'Europe/Berlin', '%Y-%m-%d' %}",
'browser_steps-1-operation': 'Click element',
'browser_steps-1-selector': 'button[name=test-button]',
'browser_steps-1-optional_value': '',
# For now, cookies doesnt work in headers because it must be a full cookiejar object # For now, cookies doesnt work in headers because it must be a full cookiejar object
'headers': "testheader: yes\buser-agent: MyCustomAgent", 'headers': "testheader: yes\buser-agent: MyCustomAgent",
}, },
@@ -119,7 +127,7 @@ def test_basic_browserstep(client, live_server, measure_memory_usage):
assert b"unpaused" in res.data assert b"unpaused" in res.data
wait_for_all_checks(client) wait_for_all_checks(client)
uuid = extract_UUID_from_client(client) uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
assert live_server.app.config['DATASTORE'].data['watching'][uuid].history_n >= 1, "Watch history had atleast 1 (everything fetched OK)" assert live_server.app.config['DATASTORE'].data['watching'][uuid].history_n >= 1, "Watch history had atleast 1 (everything fetched OK)"
assert b"This text should be removed" not in res.data assert b"This text should be removed" not in res.data
@@ -132,13 +140,32 @@ def test_basic_browserstep(client, live_server, measure_memory_usage):
assert b"This text should be removed" not in res.data assert b"This text should be removed" not in res.data
assert b"I smell JavaScript because the button was pressed" in res.data assert b"I smell JavaScript because the button was pressed" in res.data
assert b'Hello-Jinja2-20' in res.data
assert b"testheader: yes" in res.data assert b"testheader: yes" in res.data
assert b"user-agent: mycustomagent" in res.data assert b"user-agent: mycustomagent" in res.data
live_server.stop()
def test_non_200_errors_report_browsersteps(client, live_server):
live_server.stop()
live_server.start()
four_o_four_url = url_for('test_endpoint', status_code=404, _external=True) four_o_four_url = url_for('test_endpoint', status_code=404, _external=True)
four_o_four_url = four_o_four_url.replace('localhost.localdomain', 'cdio') four_o_four_url = four_o_four_url.replace('localhost.localdomain', 'cdio')
four_o_four_url = four_o_four_url.replace('localhost', 'cdio') four_o_four_url = four_o_four_url.replace('localhost', 'cdio')
res = client.post(
url_for("form_quick_watch_add"),
data={"url": four_o_four_url, "tags": '', 'edit_and_watch_submit_button': 'Edit > Watch'},
follow_redirects=True
)
assert b"Watch added in Paused state, saving will unpause" in res.data
assert os.getenv('PLAYWRIGHT_DRIVER_URL'), "Needs PLAYWRIGHT_DRIVER_URL set for this test"
uuid = next(iter(live_server.app.config['DATASTORE'].data['watching']))
# now test for 404 errors # now test for 404 errors
res = client.post( res = client.post(
url_for("edit_page", uuid=uuid, unpause_on_save=1), url_for("edit_page", uuid=uuid, unpause_on_save=1),
@@ -153,12 +180,14 @@ def test_basic_browserstep(client, live_server, measure_memory_usage):
follow_redirects=True follow_redirects=True
) )
assert b"unpaused" in res.data assert b"unpaused" in res.data
wait_for_all_checks(client) wait_for_all_checks(client)
res = client.get(url_for("index")) res = get_index(client)
assert b'Error - 404' in res.data assert b'Error - 404' in res.data
client.get( client.get(
url_for("form_delete", uuid="all"), url_for("form_delete", uuid="all"),
follow_redirects=True follow_redirects=True
) )