Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Cypress’s Node-side after:run event to send a compact test summary to your API or Telegram after cypress run completes. For parallel CI runs, send the single notification from a final aggregation step instead: each machine fires its own event, which can otherwise produce duplicate or incomplete alerts.

Choose the right way to deliver results

Use after:run when you want a short summary from the Cypress runner. The callback receives run totals and metadata, and Cypress awaits a promise returned by the callback. Because the event runs in Node rather than in a browser test, make the request with a Node HTTP client—not cy.request().

  • Owned API: Send selected totals and metadata to your endpoint. Use this when another system needs to store, route, or process the result.
  • Telegram: Send a concise alert to a chat when people need to see a pass/fail summary promptly.
  • Reporter artifact: Generate a JUnit XML or Mochawesome JSON report when recipients need test names, stack traces, or a durable record.
  • Cypress Cloud webhook: If you use Cypress Cloud, its webhooks can send selected events, including a run finishing, to an endpoint you own.

A short notification is not a substitute for a detailed report. For useful failure context, preserve a report artifact and link to it or upload it separately.

Post a summary to your API

Put the event handler in your Cypress configuration file’s setupNodeEvents function. The example below uses Node’s built-in HTTPS client and posts a deliberately small JSON payload. It expects an API that accepts JSON and an API key in an Authorization: Bearer header; change the endpoint, authentication header, and field names to match your receiving API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (8GB RAM)
  • Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM)
  • Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
  • CanaKit Turbine Black Case for the Raspberry Pi 5
  • CanaKit Low Noise Bearing System Fan
  • Mega Heat Sink - Black Anodized

1. Add the post-run handler

const { defineConfig } = require('cypress');
const https = require('https');

function postJson(urlString, payload, headers = {}) {
  return new Promise((resolve, reject) => {
    const url = new URL(urlString);
    const body = Buffer.from(JSON.stringify(payload));
    const req = https.request(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': body.length,
        ...headers,
      },
    }, (res) => {
      let responseBody = '';
      res.setEncoding('utf8');
      res.on('data', (chunk) => { responseBody += chunk; });
      res.on('end', () => {
        if (res.statusCode < 200 || res.statusCode >= 300) {
          return reject(new Error(
            `Results API returned HTTP ${res.statusCode}: ${responseBody}`
          ));
        }
        resolve({ statusCode: res.statusCode, body: responseBody });
      });
    });
    req.on('error', reject);
    req.setTimeout(15000, () => req.destroy(new Error('Results API request timed out')));
    req.end(body);
  });
}

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      on('after:run', async (results) => {
        const payload = {
          status: results.totalFailed ? 'failed' : 'passed',
          total: results.totalTests,
          passed: results.totalPassed,
          failed: results.totalFailed,
          pending: results.totalPending,
          skipped: results.totalSkipped,
          durationMs: results.totalDuration,
          runUrl: results.runUrl || null,
        };

        try {
          await postJson(
            process.env.RESULTS_API_URL,
            payload,
            { Authorization: `Bearer ${process.env.RESULTS_API_TOKEN}` }
          );
          console.log('Posted Cypress summary to results API');
        } catch (error) {
          console.error('Could not post Cypress summary:', error.message);
          if (process.env.REQUIRE_RESULTS_DELIVERY === 'true') throw error;
        }
      });
      return config;
    },
  },
});

Save this in the Cypress config file your project uses, commonly cypress.config.js. Set RESULTS_API_URL and RESULTS_API_TOKEN in CI’s secret-variable settings, not in a committed file. Set REQUIRE_RESULTS_DELIVERY=true only if a failed notification must make the run fail. Otherwise, the example logs delivery errors while leaving test execution as the primary result.

2. Run Cypress and check delivery

Run the usual CI command, such as npx cypress run. On completion, the event awaits the HTTP request. Check the CI log for the success message or a delivery error, and check the receiving system for the posted payload. A successful connection alone is not enough: the example rejects non-2xx HTTP responses.

Rank #2
CanaKit Raspberry Pi 4 4GB Starter PRO Kit - 4GB RAM
  • Includes Raspberry Pi 4 4GB Model B with 1.5GHz 64-bit quad-core CPU (4GB RAM)
  • Includes Pre-Loaded 32GB EVO+ Micro SD Card (Class 10), USB MicroSD Card Reader
  • CanaKit Premium High-Gloss Raspberry Pi 4 Case with Integrated Fan Mount, CanaKit Low Noise Bearing System Fan
  • CanaKit 3.5A USB-C Raspberry Pi 4 Power Supply (US Plug) with Noise Filter, Set of Heat Sinks, Display Cable - 6 foot (Supports up to 4K60p)
  • CanaKit USB-C PiSwitch (On/Off Power Switch for Raspberry Pi 4)

Send a Telegram alert

Telegram’s Bot API accepts HTTPS requests at https://api.telegram.org/bot<token>/METHOD_NAME. For a basic text alert, call sendMessage with the required chat_id and text. Keep the bot token and chat ID in CI secrets. The following handler uses the same postJson helper from the API example; put that helper in the config file or a Node module imported by it.

on('after:run', async (results) => {
  const token = process.env.TELEGRAM_BOT_TOKEN;
  const chatId = process.env.TELEGRAM_CHAT_ID;
  if (!token || !chatId) throw new Error('Telegram secrets are not configured');

  const text = [
    `Cypress: ${results.totalPassed} passed, ${results.totalFailed} failed`,
    `${results.totalPending} pending, ${results.totalSkipped} skipped`,
    results.runUrl ? `Run: ${results.runUrl}` : null,
  ].filter(Boolean).join('n');

  const response = await postJson(
    `https://api.telegram.org/bot${token}/sendMessage`,
    { chat_id: chatId, text }
  );
  const telegramResult = JSON.parse(response.body);
  if (!telegramResult.ok) {
    throw new Error(`Telegram rejected the message: ${response.body}`);
  }
});

Telegram limits sendMessage text to 1–4096 characters after entity parsing. The example avoids including individual test names, which keeps alerts short and avoids formatting problems. If you add test names or other dynamic text, either disable formatting or escape the markup characters Telegram would interpret; truncate or split longer summaries so they stay within the documented limit. Check Telegram’s ok field as well as the HTTP status, because the API response contains the send result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep full reports as artifacts

Use the post-run summary for totals and a run link; use a reporter when the team needs diagnostic detail. Cypress documents JUnit XML and Mochawesome JSON reporter workflows. With multiple spec files, write one file per spec and merge them in a later CI step rather than letting files overwrite one another.

  • For JUnit output, include the [hash] token in filenames when specs run separately, then merge the files.
  • For Mochawesome, write JSON per spec and merge those files.
  • Upload or link the merged report as a durable CI artifact. If your API accepts report files, upload the artifact in a separate request or CI upload step.

Keep the notification compact even when you preserve a full report: it makes the alert readable while leaving failure details available where they belong.

Rank #4
Raspberry SC15184 Pi 4 Model B 2019 Quad Core 64 Bit WiFi Bluetooth (2GB)
  • Broadcom BCM2711, quad-core Cortex-A72 (ARM v8) 64-bit SoC @ 1. 5GHz
  • 2. 4 GHz and 5. 0 GHz IEEE 802. 11b/g/n/ac wireless LAN, Bluetooth 5. 0, BLE
  • 2 × USB 3. 0 ports, 2 x USB 2. 0 Ports
  • 2 × micro HDMI ports supproting up to 4Kp60 video resolution
  • Micro SD card slot for loading operating system and data storage

Avoid duplicate alerts in parallel CI

Cypress fires after:run once on each machine when specs run in parallel. If every machine sends a message, recipients can get several partial summaries instead of one complete run result.

  1. Let each parallel Cypress job run its specs and produce its own report files or job outputs.
  2. After all jobs finish, use a dedicated CI aggregation or finalization step to merge reports and send one notification.
  3. When the run is recorded in Cypress Cloud, use results.runUrl where available so the message can point to the recorded run.

For a run-wide alert, the final CI step is the right place to aggregate job outcomes; a runner-local callback cannot by itself wait for every other machine’s event.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
CanaKit Raspberry Pi 5 16GB Starter Kit PRO - Turbine Black (128GB Edition) (16GB RAM)
  • Includes Raspberry Pi 5 16GB with 2.4Ghz 64-bit quad-core CPU (16GB RAM)
  • Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
  • CanaKit Turbine Black Case for the Raspberry Pi 5
  • CanaKit Low Noise Bearing System Fan
  • Mega Heat Sink - Black Anodized

Consider Cypress Cloud webhooks

If Cypress Cloud is already part of your workflow, its webhooks can send HTTP requests to an endpoint you own for selected events, including a finished run. This can avoid adding runner-side notification code. The receiving endpoint still needs to process the webhook payload and handle its authentication and delivery behavior. Use the webhook documentation for the available payload, headers, and retries, then decide whether your endpoint should forward a concise Telegram message or store the event in your system.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Delivery, reliability, and cost decisions

  • Choose a failure policy deliberately. In the API example, delivery failures are logged by default; setting REQUIRE_RESULTS_DELIVERY=true makes the callback rethrow the error. Decide whether the notification is required infrastructure or best-effort convenience.
  • Keep secrets out of source control and payloads. Store credentials in CI secret variables. Send only the fields the destination needs.
  • Bound waiting time. The example’s 15-second request timeout prevents a stuck destination from holding the callback indefinitely. Tune it to your CI and endpoint needs.
  • Plan for transient failures. The example does not retry. If delivery is important, add a retry strategy or use a delivery mechanism with documented retries, and avoid creating multiple final alerts from parallel jobs.
  • Keep durable detail separate. Small JSON summaries and Telegram messages are cheap to inspect; complete XML/JSON reports should live in artifacts or a system designed to retain them.

Troubleshooting common failures

Symptom Likely cause What to check
No notification appears The event handler is not registered in the active Cypress config, the CI secrets are missing, or the final job did not run. Confirm the file is used by the run, inspect the CI log, and verify secret variable names without printing their values.
API returns a non-2xx status Wrong endpoint, authentication, request schema, or receiving-service failure. Read the response body in the logged error; verify the endpoint’s expected fields and auth header.
Telegram returns an error Invalid token or chat ID, or Telegram rejected the request. Check that secrets are correct and the response JSON has ok: true; HTTP success by itself is not the send confirmation.
Telegram text is rejected or malformed Message is too long or dynamic content conflicts with Telegram formatting. Keep a concise summary, stay within the documented text limit, and escape or disable formatting for user-controlled text.
Several messages report partial totals Every parallel machine is posting from its own after:run. Move the notification to one final CI aggregation step after all parallel jobs complete.
Report files overwrite each other Multiple specs are writing to the same reporter output filename. Use a unique JUnit [hash] filename or one Mochawesome JSON file per spec, then merge.
The CI job fails after tests finish Notification delivery is configured as mandatory and the callback rethrows delivery errors. Check whether that policy is intended; if test execution should remain authoritative, do not enable the mandatory-delivery setting.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server, not a Cypress test-results destination: use the API or Telegram integrations above to deliver test summaries. If a CI workflow also needs a clean screenshot of a page, ScreenshotNeo provides a one-request capture without setting up a browser.

ScreenshotNeo accepts cookie/consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. Sign up free for 1,000 screenshots a month, with no card required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I use cy.request() inside after:run?

No. after:run executes in Cypress’s Node event environment, not in a test’s browser context. Use a Node HTTP client there.

What if a Cypress run has no recorded Cloud URL?

The results object may not provide a runUrl; make that field optional and send null or omit it, as the examples do.

Quick Recap

Bestseller No. 1
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (8GB RAM)
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (8GB RAM)
Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM); CanaKit Turbine Black Case for the Raspberry Pi 5
$259.95
Bestseller No. 2
CanaKit Raspberry Pi 4 4GB Starter PRO Kit - 4GB RAM
CanaKit Raspberry Pi 4 4GB Starter PRO Kit - 4GB RAM
Includes Raspberry Pi 4 4GB Model B with 1.5GHz 64-bit quad-core CPU (4GB RAM); Includes Pre-Loaded 32GB EVO+ Micro SD Card (Class 10), USB MicroSD Card Reader
$159.99
Bestseller No. 4
Raspberry SC15184 Pi 4 Model B 2019 Quad Core 64 Bit WiFi Bluetooth (2GB)
Raspberry SC15184 Pi 4 Model B 2019 Quad Core 64 Bit WiFi Bluetooth (2GB)
Broadcom BCM2711, quad-core Cortex-A72 (ARM v8) 64-bit SoC @ 1. 5GHz; 2. 4 GHz and 5. 0 GHz IEEE 802. 11b/g/n/ac wireless LAN, Bluetooth 5. 0, BLE
$92.97
Bestseller No. 5
CanaKit Raspberry Pi 5 16GB Starter Kit PRO - Turbine Black (128GB Edition) (16GB RAM)
CanaKit Raspberry Pi 5 16GB Starter Kit PRO - Turbine Black (128GB Edition) (16GB RAM)
Includes Raspberry Pi 5 16GB with 2.4Ghz 64-bit quad-core CPU (16GB RAM); CanaKit Turbine Black Case for the Raspberry Pi 5
$419.99

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.