A Playwright-based slot availability monitor that continuously checks for open appointments on the Mülheim Stadt booking system and sends notifications when slots appear.
Note: This is a monitor, not a test suite. Scripts run in infinite loops and alert when an appointment becomes available.
- Node.js (v16 or later)
- npm
- Clone the repository:
git clone <repository-url>
- Navigate to the project directory:
cd Monitor-Mulheim-Stadt-Termin - Install dependencies:
npm install npx playwright install --with-deps
Monitoring for Ausländeramt covers all 49 appointment types ("Anliegen") offered across its 8 category tabs, built on a small Page Object Model so a selector fix in one place covers every monitor:
src/
pages/AnliegenPage.ts <- booking-flow Page Object (navigate, select Anliegen, confirm documents, read next Termin)
notifier.ts <- beep + terminal bell + Telegram on slot found
telegram.ts <- Telegram Bot API helper
anliegen-config.ts <- single source of truth: every {tab, name, slug} Anliegen
anliegen-monitor.ts <- shared test()-with-infinite-retry-loop runner
scripts/
generate-anliegen-tests.js <- (re)generates tests/anliegen/<tab>/*.spec.ts from anliegen-config.ts
run-all-monitors.js <- spawns one independent process per monitor
tests/anliegen/<tab>/*.spec.ts <- one thin generated spec file per Anliegen, one subfolder per category tab (49 files across 8 folders)
If the portal adds, removes, or renames an Anliegen, update src/anliegen-config.ts and re-run:
node scripts/generate-anliegen-tests.jsAll 49 Anliegen, grouped by the 8 category tabs on the portal. Each tab has its own folder under tests/anliegen/.
- Anmeldung Einzelperson —
meldewesen-anmeldung-einzelperson - Anmeldung EU-Bürger —
meldewesen-anmeldung-eu-buerger - Anmeldung Familie —
meldewesen-anmeldung-familie - Ummeldung / Abmeldung —
meldewesen-ummeldung-abmeldung
- Erteilung Aufenthaltserlaubnis oder BlueCard/EU —
allgemeine-aufenthalt-erteilung-aufenthaltserlaubnis-oder-bluecard-eu - Verlängerung Aufenthaltserlaubnis oder BlueCard/EU —
allgemeine-aufenthalt-verlaengerung-aufenthaltserlaubnis-oder-bluecard-eu - Auflagenänderung bzgl. Wohnsitznahme —
allgemeine-aufenthalt-auflagenaenderung-bzgl-wohnsitznahme - Auflagenänderung bzgl. Arbeit —
allgemeine-aufenthalt-auflagenaenderung-bzgl-arbeit - Verlängerung Fiktion —
allgemeine-aufenthalt-verlaengerung-fiktion - Ausstellung eines Reiseausweises nach der Genfer Konvention —
allgemeine-aufenthalt-ausstellung-reiseausweis-genfer-konvention - Erteilung Niederlassungserlaubnis —
allgemeine-aufenthalt-erteilung-niederlassungserlaubnis - Erteilung Niederlassungserlaubnis für Fachkräfte —
allgemeine-aufenthalt-erteilung-niederlassungserlaubnis-fachkraefte - Übertragung Aufenthaltserlaubnis / Niederlassungserlaubnis —
allgemeine-aufenthalt-uebertragung-aufenthaltserlaubnis-niederlassungserlaubnis - Ausstellung einer Aufenthaltskarte EU/Daueraufenthaltskarte —
allgemeine-aufenthalt-ausstellung-aufenthaltskarte-eu-daueraufenthaltskarte - Allgemeine Beratung —
allgemeine-aufenthalt-allgemeine-beratung - Elektronische Aufenthaltstitel (eAT) > Aktivierung Online Funktion / PIN-Änderung —
allgemeine-aufenthalt-elektronische-aufenthaltstitel-eat-aktivierung-online-pin - Abgabe einer Verpflichtungserklärung —
allgemeine-aufenthalt-abgabe-verpflichtungserklaerung
- Beratung —
eu-buerger-beratung - Einreichen Dokumente EU-Bürger —
eu-buerger-einreichen-dokumente-eu-buerger
- Anmeldung Einzelperson mit Visum —
visa-anmeldung-einzelperson-mit-visum - Anmeldung Familie mit Visum —
visa-anmeldung-familie-mit-visum - Beratung Familienzusammenführung —
visa-beratung-familienzusammenfuehrung
- Anmeldung —
max-planck-anmeldung - Ummeldung / Abmeldung —
max-planck-ummeldung-abmeldung - Erteilung Aufenthaltserlaubnis —
max-planck-erteilung-aufenthaltserlaubnis - Verlängerung Aufenthaltserlaubnis (Fiktion) —
max-planck-verlaengerung-aufenthaltserlaubnis-fiktion - Erteilung einer Niederlassungserlaubnis oder Blaue Karte EU —
max-planck-erteilung-niederlassungserlaubnis-oder-blaue-karte-eu - Allgemeine Beratung —
max-planck-allgemeine-beratung
- Anmeldung —
studierende-anmeldung - Ummeldung / Abmeldung —
studierende-ummeldung-abmeldung - Erteilung Aufenthaltserlaubnis —
studierende-erteilung-aufenthaltserlaubnis - Verlängerung Aufenthaltserlaubnis —
studierende-verlaengerung-aufenthaltserlaubnis - Auflagenänderung (Wechsel Studium, Arbeit o.ä.) —
studierende-auflagenaenderung-wechsel-studium-arbeit - Neuausstellung oder Übertragung Aufenthaltserlaubnis —
studierende-neuausstellung-oder-uebertragung-aufenthaltserlaubnis - Erteilung einer Niederlassungserlaubnis oder Blaue Karte EU —
studierende-erteilung-niederlassungserlaubnis-oder-blaue-karte-eu - Allgemeine Beratung —
studierende-allgemeine-beratung - Elektronische Aufenthaltstitel (eAT) > Aktivierung Online Funktion / PIN-Änderung —
studierende-elektronische-aufenthaltstitel-eat-aktivierung-online-pin - Abgabe einer Verpflichtungserklärung zur Sicherung des Lebensunterhaltesbei Studierenden —
studierende-abgabe-verpflichtungserklaerung-lebensunterhalt
- Beratung zur Einbürgerung —
einbuergerung-beratung-zur-einbuergerung - Abgabe des Einbürgerungsantrages —
einbuergerung-abgabe-einbuergerungsantrag
- Beratung allgemein —
asyl-beratung-allgemein - Beratung freiwillige Ausreise —
asyl-beratung-freiwillige-ausreise - Ersterteilung einer Aufenthaltsgestattung —
asyl-ersterteilung-aufenthaltsgestattung - Verlängerung einer Aufenthaltsgestattung —
asyl-verlaengerung-aufenthaltsgestattung - Erteilung einer Duldung —
asyl-erteilung-duldung - Verlängerung einer Duldung —
asyl-verlaengerung-duldung - Änderung der Arbeitsauflage (Duldung oder Aufenthaltsgestattung) —
asyl-aenderung-arbeitsauflage - Antrag auf Streichung der Wohnsitzauflage (Duldung oder Aufenthaltsgestattung) —
asyl-antrag-streichung-wohnsitzauflage - Beantragung der Ersterteilung einer Aufenthaltserlaubnis —
asyl-beantragung-ersterteilung-aufenthaltserlaubnis
There are no npm scripts — use npx playwright directly.
# Run a single monitor (one Anliegen)
npx playwright test tests/anliegen/meldewesen/meldewesen-anmeldung-einzelperson.spec.ts
# Run with visible browser
npx playwright test tests/anliegen/meldewesen/meldewesen-anmeldung-einzelperson.spec.ts --headed
# List every monitor available
npx playwright test --list
# Run every monitor at once, each as its own process (heavy: ~49 Chromium instances)
node scripts/run-all-monitors.jsEach monitor runs forever, polling every 10 minutes by default — override with MONITOR_INTERVAL_MS (milliseconds) in .env. npx playwright test without a path argument is not recommended for this repo's monitors — Playwright's worker pool is bounded (CPU core count by default), so a handful of monitors would run forever while the rest starve in the queue. Use scripts/run-all-monitors.js to run everything, since it gives each monitor its own OS process instead of relying on Playwright's worker pool.
Optionally filter by a cutoff date — only alerts if the next slot is before the given date:
BEFORE_DATE=2026-06-01 npx playwright test tests/anliegen/meldewesen/meldewesen-ummeldung-abmeldung.spec.tsBEFORE_DATE accepts YYYY-MM-DD format. run.js wraps this as a --before-date=YYYY-MM-DD flag for any single spec file.
Unlike the monitors above (which loop forever), this checks every Anliegen exactly once and exits:
npx playwright test tests/availability-report.spec.tsTakes several minutes (one sequential pass through all 49). Prints a summary grouped by tab to the console, and saves a timestamped reports/availability-report-<timestamp>.{md,json,html} (gitignored). Each item is classified available (with the Termin date), no-slot (the expected "Nächster Termin" timeout), or error (anything else — a real selector break worth investigating). Open the .html file in a browser for a readable, color-coded view grouped by tab; the .json is for programmatic use.
Unlike the monitors (which stop after finding a slot) and the one-shot report (which exits), tests/availability-sync.spec.ts loops forever like a monitor but sweeps all 49 Anliegen once per pass and writes the result to Supabase instead of notifying:
npx playwright test tests/availability-sync.spec.tsEach pass upserts one row per Anliegen into current_status (always-fresh snapshot) and appends a row to availability_events only when a status actually changed since the last pass (so restarting the job doesn't create spurious history). See supabase/migrations/0001_availability_schema.sql for the schema, and the dashboard/ app for a UI on top of it. Requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env — see "Supabase setup" below. Sweep gap defaults to 10 minutes; override with SYNC_GAP_MS (milliseconds) in .env.
- Create a project at supabase.com.
- Open the SQL editor and run
supabase/migrations/0001_availability_schema.sql. - From Project Settings → API, copy the Project URL,
anonkey, andservice_rolekey into.env(see.env.example):The sync job uses the service-role key (server-side, full write access). The dashboard uses the anon key (read-only — RLS only grantsSUPABASE_URL= SUPABASE_SERVICE_ROLE_KEY= SUPABASE_ANON_KEY=selecttoanon/authenticated).
dashboard/ is a standalone Vite + React + TypeScript app reading directly from Supabase (not from this repo's monitors/sync job at runtime):
- Current Status (
/) — all 49 Anliegen grouped by tab, live via Supabase Realtime oncurrent_status. - Historical Analysis (
/history) — charts (availability frequency per tab, average availability-window duration, error counts per Anliegen) plus a filterable recent-events table, fromavailability_eventsand theavailability_windows/daily_availability_summaryviews.
cd dashboard
cp .env.example .env # fill in VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY
npm install
npm run devThree independent services, defined in docker-compose.yml:
monitor— the existing 49 notification monitors (scripts/run-all-monitors.js), unchanged.sync— the Supabase sync job (tests/availability-sync.spec.ts).dashboard— the React app, built and served via nginx.
monitor and sync share the root Dockerfile; dashboard has its own under dashboard/Dockerfile.
cp .env.example .env # TELEGRAM_*, SUPABASE_*
docker compose up --buildThe dashboard build inlines VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEY at build time (via docker-compose.yml build args from .env) — changing them requires docker compose build dashboard, not just a restart.
When a slot is found, a monitor triggers:
- Audio: plays
media/beep.wav - Terminal: bell character printed to console
- Telegram: message via
src/telegram.ts(silently skipped if not configured)
- In Telegram, message @BotFather, send
/newbot, and follow the prompts. It replies with a token — that'sTELEGRAM_BOT_TOKEN. - Send any message to your new bot, then visit
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesand find"chat":{"id":...}— that'sTELEGRAM_CHAT_ID. (For a group, add the bot to the group first; group chat IDs are negative.) - Copy
.env.exampleto.envand fill in both values:cp .env.example .env
playwright.config.tsloads.envautomatically (viadotenv), so every monitor — including all 49 spawned byscripts/run-all-monitors.js— picks it up without re-exporting anything.
- The target site is third-party and not under our control — selectors can break without warning when the site updates. We've already seen this: the portal's internal
concerns_accordion-*/button-plus-*element IDs regenerate periodically, which is whyAnliegenPageuses role-based selectors (tab/button accessible names) instead of those IDs. - Most Anliegen will show no slot most of the time. Seeing
waiting for locator('text=Nächster Termin') to be visibletime out in the logs is the expected, correct signal — it means "no slot yet," and the monitor will retry afterMONITOR_INTERVAL_MS(default 10 minutes). - Scripts retry on failure (including "no slot found") after
MONITOR_INTERVAL_MS, default 10 minutes.
This project is licensed under the MIT License.