{
  "name": "Post-Deploy Visual Smoke Test → Slack + GitHub Issue",
  "nodes": [
    {
      "id": "fa37c1bd-c241-42af-ad91-ec32eee7e32c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        1456
      ],
      "parameters": {
        "width": 480,
        "height": 1296,
        "content": "## Post-Deploy Visual Smoke Test → Slack + GitHub Issue\n\n### How it works\n\nYour CI pipeline calls this workflow's webhook right after a deploy finishes, POSTing the deployed page's URL, a baseline image/URL to compare against, and (optionally) the commit SHA and environment name. The workflow waits 45 seconds so CDN caches, edge revalidation and client-side hydration settle, then runs one Snapshot Site **Compare** call (baseline vs. freshly deployed page). Soft API errors are caught first, then the pixel mismatch is checked against a 10% layout-drift threshold. A regression fans out to two places at once: a Slack alert with the commit, environment, mismatch score and diff/before/after links, and a GitHub issue so the failure lands in the team's tracker instead of scrolling out of a channel. A clean deploy ends quietly on the No-Op branch.\n\n### Setup steps\n\n- Install the `n8n-nodes-snapshot-site` community node (Settings → Community nodes → Install → `n8n-nodes-snapshot-site`) — verified by n8n.\n- Add a Snapshot Site credential, a Slack credential, and a GitHub credential (personal access token with `repo` scope).\n- Open **Create Visual Regression Issue on GitHub** and set the repository owner and repository name.\n- Activate the workflow, copy the Webhook node's **Production URL**, and call it from the last step of your deploy job with `{\"url\": \"https://...\", \"baselineUrl\": \"https://...\", \"commit\": \"$GITHUB_SHA\", \"environment\": \"production\"}`.\n- `url` and `baselineUrl` must be full URLs including `https://` — the API rejects bare domains with a soft error (HTTP 200 + `{error: true, message}`), which this workflow routes to the failure alert.\n- Tune the Wait node (30–60s is typical) and the mismatch percentage in **Check for Layout Regression** to your stack.\n\n### Customization\n\nThe two knobs that matter are the settle delay and the mismatch threshold: `10` is a layout-drift band, `> 0` catches character-level text changes (pricing/legal copy), `~30` is defacement-level. Mask genuinely dynamic regions with `hide` (a comma-separated CSS selector string) in `afterOptions` instead of raising the threshold. Swap the GitHub node for Jira/Linear, or drop it and keep Slack only, if your team tracks regressions elsewhere."
      },
      "typeVersion": 1
    },
    {
      "id": "4458410c-6577-4203-b1f6-00a48e66f700",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 688,
        "content": "## Receive deploy and compare\n\nCI calls the webhook when a deploy finishes. The Wait node lets CDN caches and hydration settle before capture — skipping it is the #1 source of false-positive regressions."
      },
      "typeVersion": 1
    },
    {
      "id": "666fe6da-42c3-4135-8b74-b22bd3342683",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        1936
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 592,
        "content": "## Handle compare errors\n\nCatches hard failures (network/HTTP after 3 retries) on the node's error output and soft errors (HTTP 200 + `{error: true}`) on the success output, and sends both to one Slack failure alert."
      },
      "typeVersion": 1
    },
    {
      "id": "fb853c59-fed0-4614-b95a-2b3703699fe0",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2608,
        1728
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 784,
        "content": "## Report regression result\n\nEvaluates the mismatch percentage against the 10% layout-drift threshold. A regression alerts Slack **and** files a GitHub issue in parallel; a clean deploy ends on the No-Op branch."
      },
      "typeVersion": 1
    },
    {
      "id": "908f3f9d-7008-4590-bc73-f96747f6d688",
      "name": "When a Deploy Finishes",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1504,
        2176
      ],
      "parameters": {
        "path": "deploy-finished",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "bd73c508-8fd0-4de6-9832-89ad796ed352",
      "name": "Wait for Deploy to Settle",
      "type": "n8n-nodes-base.wait",
      "position": [
        1696,
        1984
      ],
      "parameters": {
        "amount": 45
      },
      "typeVersion": 1.1
    },
    {
      "id": "b751fec8-a19b-41ea-bc6b-8136e05b3b6c",
      "name": "Compare Deploy to Baseline",
      "type": "n8n-nodes-snapshot-site.snapshotSite",
      "onError": "continueErrorOutput",
      "maxTries": 3,
      "position": [
        1856,
        2176
      ],
      "parameters": {
        "afterUrl": "={{ $json.body.url }}",
        "beforeUrl": "={{ $json.body.baselineUrl }}",
        "operation": "compare",
        "afterOptions": {
          "delay": 3,
          "fullSize": true,
          "hideCookie": true
        },
        "beforeOptions": {},
        "requestOptions": {}
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "waitBetweenTries": 5000
    },
    {
      "id": "6d605840-e5e1-4b4c-9c6d-1e0d7b1ffacc",
      "name": "Check for API Error",
      "type": "n8n-nodes-base.if",
      "position": [
        2240,
        2112
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.error === true }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b9eec29f-156c-4190-ad6c-537be4c57bd6",
      "name": "Check for Layout Regression",
      "type": "n8n-nodes-base.if",
      "position": [
        2656,
        2128
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.summary.mismatchPercentage }}",
              "value2": 10,
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dd9afa51-8c23-4baf-8130-ffd1946aa04a",
      "name": "Notify Visual Regression on Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        2928,
        1920
      ],
      "parameters": {
        "text": "=🚨 *Visual regression after deploy* — `{{ $('When a Deploy Finishes').first().json.body.environment || 'production' }}` @ `{{ $('When a Deploy Finishes').first().json.body.commit || 'unknown commit' }}`\nMismatch *{{ $json.summary.mismatchPercentage }}%* ({{ $json.summary.mismatchPixels.toLocaleString() }} of {{ $json.summary.totalPixels.toLocaleString() }} px){{ $json.summary.sameDimensions ? '' : ' · page dimensions changed' }} on {{ $json.after.source }}\n<{{ $json.diff.link }}|View diff> · <{{ $json.before.link }}|Baseline> · <{{ $json.after.link }}|Deployed>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#deploys"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "19166934-6d3d-4622-9a8e-0a85eb1b76c7",
      "name": "Create Visual Regression Issue on GitHub",
      "type": "n8n-nodes-base.github",
      "position": [
        2928,
        2112
      ],
      "parameters": {
        "body": "=A post-deploy visual smoke test detected a layout regression.\n\n| Field | Value |\n| --- | --- |\n| Environment | `{{ $('When a Deploy Finishes').first().json.body.environment || 'production' }}` |\n| Commit | `{{ $('When a Deploy Finishes').first().json.body.commit || 'unknown' }}` |\n| Deployed URL | {{ $json.after.source }} |\n| Baseline | {{ $json.before.source }} |\n| Mismatch | **{{ $json.summary.mismatchPercentage }}%** ({{ $json.summary.mismatchPixels }} of {{ $json.summary.totalPixels }} px) |\n| Same dimensions | {{ $json.summary.sameDimensions }} |\n\n### Captures\n\n- [Diff image]({{ $json.diff.link }})\n- [Baseline capture]({{ $json.before.link }})\n- [Deployed capture]({{ $json.after.link }})\n\n![Visual diff]({{ $json.diff.link }})\n\n_Filed automatically by the n8n post-deploy visual smoke test (Snapshot Site Compare)._",
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "your-org"
        },
        "title": "=🚨 Visual regression on {{ $('When a Deploy Finishes').first().json.body.environment || 'production' }}: {{ $json.summary.mismatchPercentage }}% mismatch after {{ $('When a Deploy Finishes').first().json.body.commit || 'latest deploy' }}",
        "labels": [
          {
            "label": "visual-regression"
          }
        ],
        "assignees": [],
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "your-repo"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "82b459a1-ee83-44fb-a0fe-5def749744cb",
      "name": "No Visual Regression",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2928,
        2304
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "15ee44e3-338d-4ef1-98c9-8724b91ea8d2",
      "name": "Notify Comparison Failure on Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        2432,
        2352
      ],
      "parameters": {
        "text": "=🔴 Post-deploy visual smoke test could not run for `{{ $('When a Deploy Finishes').first().json.body.environment || 'production' }}` @ `{{ $('When a Deploy Finishes').first().json.body.commit || 'unknown commit' }}`: {{ $json.error?.message || $json.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#deploys-alerts"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false
        }
      },
      "typeVersion": 2.1
    }
  ],
  "connections": {
    "Check for API Error": {
      "main": [
        [
          {
            "node": "Notify Comparison Failure on Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check for Layout Regression",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When a Deploy Finishes": {
      "main": [
        [
          {
            "node": "Wait for Deploy to Settle",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Deploy to Settle": {
      "main": [
        [
          {
            "node": "Compare Deploy to Baseline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Deploy to Baseline": {
      "main": [
        [
          {
            "node": "Check for API Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Comparison Failure on Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Layout Regression": {
      "main": [
        [
          {
            "node": "Notify Visual Regression on Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create Visual Regression Issue on GitHub",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Visual Regression",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}
