@alosha/monitor
v0.4.0
Published
Playwright-based website monitoring — zero-config uptime checks, retries, screenshots on failure, and multi-channel alerts.
Maintainers
Readme
@alosha/monitor
Playwright-based website monitoring for developers. Define checks, run multi-step journeys, assert on content, get notified on failures, and generate HTML reports — all from your terminal or CI pipeline.
- Real user journeys, not just pings — click, fill, hover, and wait through multi-step flows like login or checkout.
- Assert on what matters — page title, URL, element visibility, text content, and response time.
- Get told the moment something breaks — alerts to Slack, Discord, email, or any webhook.
- Evidence on every failure — automatic screenshots and a self-contained HTML report, runnable locally or in CI/GitHub Actions.
Install
npm install -D @alosha/monitor
npx playwright install chromiumQuick start
Create a monitor.config.ts (or .js) in your project root:
import type { MonitorConfig } from '@alosha/monitor'
export default {
checks: [
// Simple URL check
{ name: 'Homepage', url: 'https://yoursite.com', interval: '5m' },
// Multi-step journey with assertions
{
name: 'Login flow',
url: 'https://yoursite.com/login',
interval: '10m',
steps: [
{ action: 'fill', selector: '#email', value: '[email protected]' },
{ action: 'fill', selector: '#password', value: process.env.TEST_PASS! },
{ action: 'click', selector: 'button[type=submit]' },
{ action: 'waitForURL', value: '/dashboard' },
],
assertions: [
{ type: 'title', contains: 'Dashboard' },
{ type: 'visible', selector: '.welcome-message' },
{ type: 'responseTime', maxMs: 3000 },
],
},
],
notify: {
slack: { webhookUrl: process.env.SLACK_WEBHOOK_URL! },
},
} satisfies MonitorConfigThen run once:
npx monitor runOr keep it running on a schedule:
npx monitor watchProduction recipes
Real problems, complete solutions — copy, paste, ship.
Fail your CI build when a critical user journey breaks
The problem: a deploy can pass unit tests but still break login or checkout in a real browser — and you find out from a customer.
import { run } from '@alosha/monitor'
const report = await run({
checks: [{
name: 'Login flow',
url: 'https://app.example.com/login',
steps: [
{ action: 'fill', selector: '#email', value: process.env.TEST_EMAIL! },
{ action: 'fill', selector: '#password', value: process.env.TEST_PASS! },
{ action: 'click', selector: 'button[type=submit]' },
{ action: 'waitForURL', value: '**/dashboard' }
],
assertions: [{ type: 'visible', selector: '[data-test=user-menu]' }]
}]
})
// Break the build if any check failed.
if (report.failed > 0) process.exit(1)Why it works: run() drives a real Chromium session through the journey and returns a structured RunReport, so a single exit-code check turns "does login still work?" into a CI gate that blocks the deploy before users ever see the break.
Get a Slack alert with a screenshot the moment a page goes down
The problem: uptime pings tell you a URL returns 200, not that the page actually rendered — and they rarely show you what the user saw.
import { watch } from '@alosha/monitor'
// Runs continuously, firing each check on its own interval.
await watch({
checks: [
{ name: 'Homepage', url: 'https://example.com', interval: '1m', maxResponseTimeMs: 2000 },
{ name: 'Checkout', url: 'https://example.com/checkout', interval: '5m' }
],
notify: { slack: { webhookUrl: process.env.SLACK_WEBHOOK_URL! } }
})Why it works: watch() schedules each check independently and, on failure, captures a full-page screenshot before posting to Slack — so your first signal of an outage is an alert with visual proof, not a support ticket.
Alert semantics in watch mode
watch() notifies only on status transitions, not on every failing run: you get one alert when a check goes from ok to failing, and one recovery alert when it goes back to ok. A site that's down for an hour at interval: '1m' sends exactly one alert, not sixty.
For outages you want to be reminded about while they're ongoing, set reAlertInterval on the check (e.g. '30m') to re-send the down alert on that cadence until it recovers. It defaults to off — sustained failures stay silent between the initial alert and the recovery alert.
{ name: 'Checkout', url: 'https://example.com/checkout', interval: '1m', reAlertInterval: '30m' }This state is per-process: restarting watch() forgets prior status, so the first result for each check after a restart is treated as a transition if it's a failure (you'll get a fresh down alert, not silence).
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| checks | CheckConfig[] | required | List of checks to run |
| notify | NotifyConfig | — | Alert destinations |
| screenshotsDir | string | ./monitor-screenshots | Where to save failure screenshots |
| reportsDir | string | . | Where to save monitor-report.html |
| concurrency | number | 4 | Max checks running at once against the shared browser |
CheckConfig
| Option | Type | Default | Description |
|---|---|---|---|
| name | string | required | Human-readable label |
| url | string | required | Full URL to navigate to |
| interval | string | "5m" | Watch mode interval. Supports "30s", "5m", "1h", "2h30m" |
| reAlertInterval | string | off | Watch mode: re-send the down alert on this cadence while a check stays failing |
| retries | number | 2 | Retry attempts before marking failed |
| timeout | number | 10000 | Timeout per action in ms |
| screenshotOnFailure | boolean | true | Save a screenshot on failure |
| steps | StepAction[] | — | Multi-step journey actions |
| assertions | Assertion[] | — | Assertions to verify after steps |
| maxResponseTimeMs | number | — | Shorthand for { type: 'responseTime', maxMs } assertion |
Steps
steps: [
{ action: 'click', selector: 'button' },
{ action: 'fill', selector: '#email', value: '[email protected]' },
{ action: 'select', selector: 'select#plan', value: 'pro' },
{ action: 'hover', selector: '.dropdown' },
{ action: 'waitForSelector', selector: '.modal' },
{ action: 'waitForURL', value: '/dashboard' },
{ action: 'waitForLoadState', value: 'networkidle' },
{ action: 'screenshot', name: 'after-login' },
]Assertions
assertions: [
{ type: 'title', contains: 'Dashboard' },
{ type: 'url', contains: '/dashboard' },
{ type: 'visible', selector: '.welcome-message' },
{ type: 'text', selector: 'h1', contains: 'Welcome' },
{ type: 'responseTime', maxMs: 3000 },
]Notifications
notify: {
email: { to, from, smtpHost, smtpPort?, smtpUser, smtpPass },
slack: { webhookUrl },
discord: { webhookUrl },
webhook: { url, headers? },
}GitHub Actions
Drop this in .github/workflows/monitor.yml to run your checks every 15 minutes in CI:
name: Monitor
on:
schedule:
- cron: '*/15 * * * *'
workflow_dispatch:
jobs:
monitor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- run: npx playwright install chromium --with-deps
- run: npx monitor run
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: monitor-report
path: |
monitor-report.html
monitor-screenshots/Programmatic usage
import { run, watch } from '@alosha/monitor'
// One-shot run
const report = await run({ checks: [{ name: 'Homepage', url: 'https://example.com' }] })
console.log(report.passed, report.failed)
// Continuous watch
await watch({ checks: [{ name: 'Homepage', url: 'https://example.com', interval: '5m' }] })Support & custom work
@alosha/monitor is free and MIT-licensed, and always will be. When you need more than the open-source CLI, there's a paid path backed by the maintainer — not a ticket queue:
- Priority support — a direct line to the person who wrote it, with prioritised fixes.
- Custom work — bespoke checks, assertions or notifier integrations, and help wiring Monitor into your CI/CD.
Get in touch at alosha.dev/support.
Docs & live demo: monitor.alosha.dev · Built by Alosha
