running-smoke-tests

Execute fast smoke tests validating critical functionality after deployment. Use when performing specialized testing. Trigger with phrases like "run smoke tests", "quick validation", or "test critical paths".

claude-codecodexopenclaw
6 Tools
smoke-test-runner Plugin
testing Category

Allowed Tools

ReadWriteEditGrepGlobBash(test:smoke-*)

Provided by Plugin

smoke-test-runner

Quick smoke test suites to verify critical functionality after deployments

testing v1.0.0
View Plugin

Installation

This skill is included in the smoke-test-runner plugin:

/plugin install smoke-test-runner@claude-code-plugins-plus

Click to copy

Instructions

Smoke Test Runner

Overview

Execute fast, high-confidence smoke tests that validate critical application functionality after deployment or build. Smoke tests verify that the application starts, core user flows work, and key integrations respond -- without running the full test suite.

Prerequisites

  • Application deployed and accessible at a known URL or running locally
  • HTTP client available (curl, wget, node-fetch, or Playwright)
  • List of critical endpoints and user flows to validate
  • Expected response codes and content patterns for each check
  • CI/CD pipeline hook for post-deployment validation

Instructions

  1. Identify the critical paths that constitute a "working" application:
  • Health check endpoint returns 200 with expected body.
  • Homepage loads and contains key UI elements.
  • Authentication flow succeeds with test credentials.
  • Primary API endpoint returns valid data.
  • Database connection is active and responding.
  1. Create a smoke test configuration listing each check:
  • URL or command to execute.
  • Expected HTTP status code (200, 301, etc.).
  • Response body pattern to match (substring or regex).
  • Maximum acceptable response time (e.g., 3 seconds).
  1. Write the smoke test suite as a lightweight script or test file:
  • Use curl for HTTP checks or Playwright for browser-based checks.
  • Run checks sequentially for simplicity (parallel for speed if independent).
  • Fail fast on the first critical failure.
  • Log each check result with pass/fail, response time, and status code.
  1. Implement timeout guards:
  • Set a global timeout of 60 seconds for the entire smoke suite.
  • Set per-check timeouts of 5-10 seconds.
  • Treat timeouts as failures, not retries.
  1. Add deployment-gate integration:
  • On success: proceed with deployment promotion or traffic shifting.
  • On failure: trigger rollback and send alert notification.
  • Report results to CI/CD dashboard and Slack/Teams webhook.
  1. Store smoke test results as CI artifacts for audit trail.
  2. Schedule periodic smoke runs (every 5 minutes in production) as synthetic monitoring.

Output

  • Smoke test script (scripts/smoke-test.sh or tests/smoke.test.ts)
  • Pass/fail result for each critical check with response times
  • Deployment gate verdict (PASS or FAIL with reason)
  • CI artifact with timestamped smoke test log
  • Alert payload for failed checks (Slack webhook, PagerDuty, etc.)

Error Handling

Error Cause Solution
Connection refused Application not yet ready after deployment Add a startup wait with exponential backoff (max 30 seconds) before running smoke tests
503 Service Unavailable Application is starting or behind a load balancer draining Retry with 2-second delay up to 3 times; check load balancer health check status
Unexpected redirect (301/302) URL changed or SSL redirect not accounted for Follow redirects with curl -L; update expected URLs in smoke config
Content mismatch Page content changed but smoke test pattern is too specific Use broad patterns (check for </code> or key element IDs, not exact text)</td> </tr> <tr> <td>Timeout on database check</td> <td>Database migration running or connection pool exhausted</td> <td>Increase timeout for database checks; verify migration completed before smoke tests</td> </tr> </tbody></table> <h2>Examples</h2> <p><strong>Shell-based smoke test script:</strong></p> <pre data-lang="bash"><code> #!/bin/bash set -e BASE_URL="${1:-http://localhost:3000}" # 3000: 3 seconds in ms PASS=0; FAIL=0 check() { local name="$1" url="$2" expected="$3" status=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url") if [ "$status" = "$expected" ]; then echo "PASS: $name (HTTP $status)" ((PASS++)) else echo "FAIL: $name (expected $expected, got $status)" ((FAIL++)) fi } check "Health check" "$BASE_URL/health" "200" # HTTP 200 OK check "Homepage" "$BASE_URL/" "200" # HTTP 200 OK check "API status" "$BASE_URL/api/status" "200" # HTTP 200 OK check "Login page" "$BASE_URL/login" "200" # HTTP 200 OK echo "Results: $PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ] || exit 1 </code></pre> <p><strong>Playwright smoke test:</strong></p> <pre data-lang="typescript"><code> import { test, expect } from '@playwright/test'; test('homepage loads with navigation', async ({ page }) => { await page.goto('/', { timeout: 10000 }); # 10000: 10 seconds in ms await expect(page.locator('nav')).toBeVisible(); await expect(page).toHaveTitle(/My App/); }); test('API health endpoint responds', async ({ request }) => { const response = await request.get('/api/health'); expect(response.ok()).toBeTruthy(); expect(await response.json()).toHaveProperty('status', 'ok'); }); </code></pre> <h2>Resources</h2> <ul> <li>Smoke testing methodology: https://martinfowler.com/bliki/SmokeTest.html</li> <li>Playwright API testing: https://playwright.dev/docs/api-testing</li> <li>curl documentation: https://curl.se/docs/manpage.html</li> <li>GitHub Actions deployment gates: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment</li> </ul></div> </div> </section> <!-- 8. Bottom CTA --> <section class="detail-cta" data-astro-cid-qcigfm7x> <h2 class="detail-cta__headline" data-astro-cid-qcigfm7x>Ready to use smoke-test-runner?</h2> <div class="detail-cta__actions" data-astro-cid-qcigfm7x> <button class="detail-cta__primary" data-copy-install="/plugin install smoke-test-runner@claude-code-plugins-plus" data-astro-cid-qcigfm7x> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-astro-cid-qcigfm7x> <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" stroke-width="1.5" data-astro-cid-qcigfm7x></rect> <path d="M3 11V3a1 1 0 011-1h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" data-astro-cid-qcigfm7x></path> </svg> <span class="detail-cta__btn-text" data-astro-cid-qcigfm7x>Copy Install Command</span> </button> </div> </section> <script type="module">document.querySelectorAll("[data-copy-install]").forEach(t=>{t.addEventListener("click",async()=>{const a=t.dataset.copyInstall||"";await navigator.clipboard.writeText(a);const e=t.querySelector(".detail-cta__btn-text");if(e){const o=e.textContent;e.textContent="Copied!",t.classList.add("copied"),window.trackEvent&&window.trackEvent("install_copied",{plugin_name:a.split(" ").pop()||"",source:"detail_cta"}),setTimeout(()=>{e.textContent=o,t.classList.remove("copied")},2e3)}})});</script> <!-- 9. Related Skills (6, with tool pills) --> <section class="related-skills-section" data-astro-cid-jrlgpo3w> <h2 class="related-skills-heading" data-astro-cid-jrlgpo3w>Related Skills</h2> <div class="related-skills-grid" data-astro-cid-jrlgpo3w> <a href="/skills/analyzing-test-coverage/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>analyzing-test-coverage</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Analyze code coverage metrics and identify untested code paths.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/automating-api-testing/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>automating-api-testing</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Test automate API endpoint testing including request generation, validation, and comprehensive test coverage for REST and GraphQL APIs.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/fuzzing-apis/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>fuzzing-apis</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Configure perform API fuzzing to discover edge cases, crashes, and security vulnerabilities.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/generating-test-data/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>generating-test-data</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Generate realistic test data including edge cases and boundary conditions.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/generating-test-doubles/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>generating-test-doubles</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Generate mocks, stubs, spies, and fakes for dependency isolation.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/generating-test-reports/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>generating-test-reports</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Generate comprehensive test reports with metrics, coverage, and visualizations.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a> </div> </section> <!-- 10. Footer --> <footer class="skill-footer" data-astro-cid-jrlgpo3w> <p data-astro-cid-jrlgpo3w>Part of <a href="/plugins/smoke-test-runner/" data-astro-cid-jrlgpo3w>smoke-test-runner</a></p> </footer> </div> <script> document.querySelectorAll('.install-command').forEach(el => { el.addEventListener('click', function() { const text = this.dataset.copy || this.textContent; navigator.clipboard.writeText(text).then(() => { const originalText = this.textContent; this.textContent = 'Copied to clipboard!'; this.style.color = 'var(--brand-green)'; setTimeout(() => { this.textContent = originalText; this.style.color = ''; }, 2000); }); }); }); </script> </main> <!-- Footer --> <footer data-astro-cid-37fxchfa> <div class="footer-inner" data-astro-cid-37fxchfa> <div class="footer-signup-row" data-astro-cid-37fxchfa> <h4 data-astro-cid-37fxchfa>Stay in the Loop</h4> <form data-signup-form="footer" class="footer-signup-form" data-astro-cid-37fxchfa> <input type="email" name="email" placeholder="you@example.com" required data-astro-cid-37fxchfa> <input type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0;height:0;width:0;" data-astro-cid-37fxchfa> <button type="submit" data-astro-cid-37fxchfa>Subscribe</button> </form> <p class="footer-form-note" data-astro-cid-37fxchfa>No spam. Unsubscribe anytime.</p> </div> <div class="footer-columns" data-astro-cid-37fxchfa> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Product</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/explore" data-astro-cid-37fxchfa>Explore</a></li> <li data-astro-cid-37fxchfa><a href="/skills" data-astro-cid-37fxchfa>Skills</a></li> <li data-astro-cid-37fxchfa><a href="/cowork" data-astro-cid-37fxchfa>Cowork</a></li> <li data-astro-cid-37fxchfa><a href="/compare-marketplaces" data-astro-cid-37fxchfa>Compare</a></li> <li data-astro-cid-37fxchfa><a href="/tools" data-astro-cid-37fxchfa>Tools</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Resources</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/docs" data-astro-cid-37fxchfa>Docs</a></li> <li data-astro-cid-37fxchfa><a href="/changelog" data-astro-cid-37fxchfa>Changelog</a></li> <li data-astro-cid-37fxchfa><a href="/collections" data-astro-cid-37fxchfa>Collections</a></li> <li data-astro-cid-37fxchfa><a href="/playbooks" data-astro-cid-37fxchfa>Playbooks</a></li> <li data-astro-cid-37fxchfa><a href="/research" data-astro-cid-37fxchfa>Research</a></li> <li data-astro-cid-37fxchfa><a href="/learning" data-astro-cid-37fxchfa>Learning</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Company</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/community" data-astro-cid-37fxchfa>Community</a></li> <li data-astro-cid-37fxchfa><a href="/spotlight" data-astro-cid-37fxchfa>Spotlight</a></li> <li data-astro-cid-37fxchfa><a href="https://github.com/jeremylongshore/claude-code-plugins" target="_blank" data-astro-cid-37fxchfa>GitHub</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Legal</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/privacy" data-astro-cid-37fxchfa>Privacy</a></li> <li data-astro-cid-37fxchfa><a href="/terms" data-astro-cid-37fxchfa>Terms</a></li> <li data-astro-cid-37fxchfa><a href="/acceptable-use" data-astro-cid-37fxchfa>Acceptable Use</a></li> </ul> </div> </div> <div class="footer-bottom" data-astro-cid-37fxchfa> <p class="footer-blurb" data-astro-cid-37fxchfa>Tons of Skills by <a href="https://intentsolutions.io" target="_blank" data-astro-cid-37fxchfa>Intent Solutions</a>. Marine. Citadel Grad. 20 years ops → self-taught dev → AI architect.</p> <p class="footer-copyright" data-astro-cid-37fxchfa>© 2026 Tons of Skills | <a href="https://intentsolutions.io" target="_blank" data-astro-cid-37fxchfa>Intent Solutions</a></p> </div> </div> </footer> <!-- Light theme global overrides (must be unscoped to target body/nav/footer) --> <!-- Email Signup Form Handler --> <script type="module"> import { getFunctions, httpsCallable } from 'https://www.gstatic.com/firebasejs/11.8.1/firebase-functions.js'; import { getApp } from 'https://www.gstatic.com/firebasejs/11.8.1/firebase-app.js'; function initSignupForms() { var functions; try { functions = getFunctions(getApp()); } catch (e) { console.warn('[TonsOfSkills] Firebase not initialized — signup forms disabled:', e.message); document.querySelectorAll('[data-signup-form] button').forEach(function(btn) { btn.disabled = true; btn.textContent = 'Unavailable'; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; }); return; } var subscribeEmail = httpsCallable(functions, 'subscribeEmail'); document.querySelectorAll('[data-signup-form]').forEach(function(form) { form.addEventListener('submit', async function(e) { e.preventDefault(); var input = form.querySelector('input[type="email"]'); const honeypot = form.querySelector('input[name="website"]'); var btn = form.querySelector('button'); var originalText = btn.textContent; btn.textContent = 'Subscribing...'; btn.disabled = true; try { var result = await subscribeEmail({ email: input.value, source: form.dataset.signupForm, website: honeypot ? honeypot.value : '' }); if (result.data.status === 'already_subscribed') { btn.textContent = 'Already on it!'; } else { btn.textContent = 'You\'re in!'; } btn.style.opacity = '0.85'; input.value = ''; if (window.logFirebaseEvent) { window.logFirebaseEvent('email_signup', { source: form.dataset.signupForm }); } } catch (err) { if (err.code === 'invalid-argument' && (!input.value || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value))) { btn.textContent = 'Invalid Email'; } else { btn.textContent = 'Something went wrong'; } setTimeout(function() { btn.textContent = originalText; btn.disabled = false; btn.style.opacity = ''; }, 3000); } }); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initSignupForms); } else { initSignupForms(); } </script> <!-- Nomination Form Handler --> <script type="module"> import { getFunctions, httpsCallable } from 'https://www.gstatic.com/firebasejs/11.8.1/firebase-functions.js'; import { getApp } from 'https://www.gstatic.com/firebasejs/11.8.1/firebase-app.js'; function initNominationForms() { var functions; try { functions = getFunctions(getApp()); } catch (e) { document.querySelectorAll('[data-nomination-form] button').forEach(function(btn) { btn.disabled = true; btn.textContent = 'Unavailable'; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; }); return; } var submitNomination = httpsCallable(functions, 'submitNomination'); var ghPattern = /^https:\/\/github\.com\/[^\/]+\/[^\/\s]+/; document.querySelectorAll('[data-nomination-form]').forEach(function(form) { form.addEventListener('submit', async function(e) { e.preventDefault(); var input = form.querySelector('input[name="repo"]'); var honeypot = form.querySelector('input[name="website"]'); var btn = form.querySelector('button'); var originalText = btn.textContent; if (!ghPattern.test(input.value)) { btn.textContent = 'GitHub URLs only'; setTimeout(function() { btn.textContent = originalText; btn.disabled = false; }, 3000); return; } btn.textContent = 'Submitting...'; btn.disabled = true; try { var result = await submitNomination({ repoUrl: input.value, source: form.dataset.nominationForm, website: honeypot ? honeypot.value : '' }); btn.textContent = result.data.status === 'already_nominated' ? 'Already submitted!' : 'Submitted!'; btn.style.opacity = '0.85'; input.value = ''; if (window.logFirebaseEvent) { window.logFirebaseEvent('killer_skill_nomination', { source: form.dataset.nominationForm }); } } catch (err) { btn.textContent = 'Something went wrong'; setTimeout(function() { btn.textContent = originalText; btn.disabled = false; btn.style.opacity = ''; }, 3000); } }); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initNominationForms); } else { initNominationForms(); } </script> <!-- Theme Toggle Script --> <script> document.addEventListener('DOMContentLoaded', function() { var themeToggle = document.querySelector('.theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', function() { var current = document.documentElement.getAttribute('data-theme') || 'dark'; var next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('theme', next); var meta = document.querySelector('meta[name="theme-color"]'); if (meta) meta.setAttribute('content', next === 'dark' ? '#1a1816' : '#f5f3f0'); }); } window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function(e) { if (!localStorage.getItem('theme')) { document.documentElement.setAttribute('data-theme', e.matches ? 'light' : 'dark'); } }); }); </script> <!-- Nav Scroll State --> <script> (function() { var nav = document.querySelector('nav'); if (!nav) return; function onScroll() { if (window.scrollY > 20) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); </script> <!-- Mobile Menu Script --> <script> document.addEventListener('DOMContentLoaded', function() { var menuToggle = document.querySelector('.mobile-menu-toggle'); var navLinks = document.querySelector('.nav-links'); var menuIcon = document.querySelector('.menu-icon'); var closeIcon = document.querySelector('.close-icon'); if (menuToggle && navLinks) { menuToggle.addEventListener('click', function() { navLinks.classList.toggle('active'); document.body.classList.toggle('menu-open'); if (navLinks.classList.contains('active')) { menuIcon.style.display = 'none'; closeIcon.style.display = 'block'; } else { menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; } }); navLinks.querySelectorAll('a').forEach(function(link) { link.addEventListener('click', function() { navLinks.classList.remove('active'); document.body.classList.remove('menu-open'); menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; }); }); document.addEventListener('click', function(e) { if (navLinks.classList.contains('active') && !navLinks.contains(e.target) && !menuToggle.contains(e.target)) { navLinks.classList.remove('active'); document.body.classList.remove('menu-open'); menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; } }); } // Track outbound link clicks (nav + footer) document.querySelectorAll('nav a[target="_blank"], footer a[target="_blank"]').forEach(function(link) { link.addEventListener('click', function() { if (window.trackEvent) { window.trackEvent('outbound_click', { url: link.href, link_text: link.textContent.trim() }); } }); }); }); </script> </body> </html>