New to Kestra?
Use blueprints to kickstart your first workflows.
Audit a page for tracking cookies set before consent using Selenium, classify the result, and alert Slack on violation.
Under GDPR and similar privacy regimes, non-essential tracking cookies are only supposed to be set after a visitor consents, not the moment the page loads. Marketing tags, A/B test snippets, and third-party pixels routinely violate this, and it is invisible from the outside unless someone actually opens dev tools and checks document.cookie before touching the consent banner. This blueprint uses plugin-selenium's Browse task to do exactly that, automatically and on a schedule.
audit_page_for_tracking_cookies (io.kestra.plugin.selenium.Browse) opens a session against the configured Selenium Grid, navigates to target_url, immediately reads document.cookie via an EXECUTE_SCRIPT action and filters it against tracked_cookie_names in JavaScript, then checks for a consent banner via EXTRACT_TEXT with multiple: true so a missing banner returns an empty list instead of failing the task. allowFailure: true lets the flow continue instead of aborting if the Grid or the page itself is unreachable.classify_tracking_compliance (io.kestra.plugin.core.debug.Return) checks whether outputs.audit_page_for_tracking_cookies.scriptResults.tracking_check is defined at all. If it is missing, the classification is PROBE_FAILED. If the filtered list has at least one entry, the classification is PRE_CONSENT_TRACKING_DETECTED; otherwise NO_TRACKING_DETECTED.route_by_tracking_compliance (io.kestra.plugin.core.flow.Switch) branches on that string. PRE_CONSENT_TRACKING_DETECTED runs evaluate_alert_toggle (io.kestra.plugin.core.flow.If), which pages Slack via dispatch_compliance_alert only when send_slack_alert is true, naming the matched cookies and whether a banner was even present, logging a suppressed-alert line otherwise. NO_TRACKING_DETECTED logs a clean audit line. PROBE_FAILED pages Slack directly since compliance status is now unknown. The defaults branch logs a fallback line that is unreachable under normal operation.log_audit_summary records a status line on every run regardless of branch, so execution history reads as a continuous compliance trend.errors block pages Slack separately if the sentinel flow itself fails outright.cookie_compliance_schedule trigger polls every 6 hours with explicit input overrides, shipped disabled: true until pointed at a real page.status output (NO_TRACKING_DETECTED / PRE_CONSENT_TRACKING_DETECTED / PROBE_FAILED) other flows can gate on, for example before sign-off on a new landing page.A one-off manual check in dev tools catches a violation once and then nobody repeats it after the next deploy. This flow keeps every audit in Kestra's execution history, turns the result into a typed status output other flows (deployment gates, legal sign-off workflows) can depend on, and gives you an If/Switch branch to grow into real remediation (blocking a release, opening a ticket) without rewriting the probe itself.
SELENIUM_GRID_URL and SLACK_WEBHOOK secrets.https://example.com) and confirm status reads NO_TRACKING_DETECTED.target_url and consent_banner_selector at your real site.cookie_compliance_schedule.Browse task that clicks the consent banner's accept button and re-reads document.cookie, so you can confirm tracking cookies appear only after that click, not only that they are absent before it.SCREENSHOT action to capture visual evidence of the banner (or its absence) alongside the cookie check, for an audit trail a non-technical reviewer can read.Loop task, reusing the same two actions per URL.status into a KV store to alert only on state transitions instead of every run.SELENIUM_GRID_URL: URL of the Selenium Grid or WebDriver endpoint, for example http://localhost:4444.SLACK_WEBHOOK: Slack incoming webhook used for the violation, probe-failure, and flow-failure alerts.SECRET_SELENIUM_GRID_URL and SECRET_SLACK_WEBHOOK as base64, for example echo -n "<your-value>" | base64.target_url (STRING, default https://example.com): page to audit.consent_banner_selector (STRING, default a generic cookie-banner selector guess): CSS selector for the consent banner; replace with your real site's selector.tracked_cookie_names (ARRAY of STRING, default ["_ga", "_gid", "_gat", "_fbp", "_gcl_au"]): cookie name prefixes to check for.send_slack_alert (STRING, default "true"): when "true", page Slack on a violation; when "false", only log it.{{ outputs.audit_page_for_tracking_cookies.scriptResults.tracking_check }}: list of tracked cookie names found in document.cookie immediately after navigation.{{ outputs.audit_page_for_tracking_cookies.extracted.banner_matches }}: list of text content from elements matching consent_banner_selector; empty when no banner was found.{{ outputs.classify_tracking_compliance.value }}: classification string, one of NO_TRACKING_DETECTED, PRE_CONSENT_TRACKING_DETECTED, PROBE_FAILED.{{ outputs.status }}: flow-level alias of the same classification.document.cookie immediately after NAVIGATE returns. Trackers that fire after a delay, on scroll, or on another async event will not be caught by this single immediate check; this blueprint intentionally checks the worst, most common case (cookies set at load time) and documents this limit rather than claiming full coverage.document.cookie only exposes cookies that are not marked HttpOnly; an HttpOnly tracking cookie set by a first-party response header would not appear here even if it violates the same consent rule. This is a browser platform limitation, not a plugin limitation.consent_banner_selector default is a generic guess across common banner markup patterns; verify it actually matches your site's real banner before trusting a NO_TRACKING_DETECTED combined with an empty banner_matches list, that combination could just as easily mean the selector is wrong as mean there is no banner.EXTRACT_TEXT is used with multiple: true specifically so a missing banner does not fail the whole task; without multiple: true, a non-matching selector throws and the run would always classify as PROBE_FAILED instead of distinguishing a missing banner from a real probe failure.To start a local Selenium Grid for testing, execute:
docker run -d --name kestra-selenium -p 4444:4444 -v /dev/shm:/dev/shm selenium/standalone-chrome
Once running, the Grid UI is reachable at http://localhost:4444; use this same address as SELENIUM_GRID_URL.
If Kestra is running inside a Docker network, connect the Grid container to Kestra's network:
docker network connect YOUR_KESTRA_NETWORK kestra-selenium
(Run docker network ls to verify your active Kestra network name.)
