Playwright-based backend proxy for automating NoRisk international event insurance form submissions. This system acts as an intermediary between an Italian frontend and the Dutch NoRisk backend.
- Navigate NoRisk's multi-step insurance form via agent portal
- Authenticate with NoRisk agent credentials
- Extract CSRF tokens and maintain session state
- Fill event, location, and coverage details
- Submit proposals and extract quote codes
- Receive PDFs via IMAP and forward to users via SMTP
- Provide admin dashboard for quote management
- Support headless and debug modes
Italian Frontend (IT) β Express API β Playwright Automation β NoRisk Agent Portal (NL)
β
SQLite Database (quotes, sessions)
β
IMAP (receive) / SMTP (send) Email
β
Proposal HTML + PDF (returned)
src/
βββ index.js # Main Express server entry point
βββ config/
β βββ constants.js # Configuration, selectors, environment variables
βββ automation/
β βββ scraper.js # Main automation orchestration
β βββ formFiller.js # Form filling utilities
βββ utils/
β βββ dataMapper.js # Italian β Dutch data mapping & validation
β βββ logger.js # Winston logging configuration
β βββ storage.js # Quote persistence (SQLite)
β βββ db.js # Database initialization & queries
β βββ emailSender.js # SMTP email sending
β βββ emailReceiver.js # IMAP email polling
βββ admin/
βββ middleware/
β βββ auth.js # Admin authentication middleware
βββ routes/
β βββ login.js # Admin login routes
β βββ dashboard.js # Dashboard API routes
β βββ users.js # User management routes
βββ public/
βββ index.html # Admin dashboard UI
βββ admin.js # Dashboard frontend JavaScript
- Node.js v18 or higher
- Windows/Linux/macOS
- Internet connection
- NoRisk agent account credentials
- IMAP/SMTP credentials (for email features)
- Install dependencies:
npm install- Install Playwright browsers:
npx playwright install chromium- Create environment file:
cp .env.example .env- Configure
.envfile:
# Server Configuration
NODE_ENV=development
PORT=3000
DOMAIN=http://localhost:3000
# NoRisk Agent Portal Authentication
NORISK_EMAIL=your_agent@example.com
NORISK_PASSWORD=your_password
# Target URLs
LOGIN_URL=https://verzekeren.norisk.eu/agents
FORM_URL=https://verzekeren.norisk.eu/en/agents/product/event-int
NORISK_TP_PARAM=30028
# Playwright Configuration
HEADLESS=false
TIMEOUT=30000
SLOW_MO=100
FIELD_DELAY=500
# Logging
LOG_LEVEL=debug
# Automation Mode
COMPLETED=false
# Email Reception (IMAP)
IMAP_HOST=imap.gmail.com
IMAP_PORT=993
IMAP_USER=your_email@example.com
IMAP_PASS=your_app_password
IMAP_TLS=true
IMAP_CHECK_INTERVAL=30000
IMAP_MAX_WAIT=300000
# Email Sending (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@example.com
SMTP_PASS=your_app_password
SMTP_FROM=noreply@example.com
SMTP_SECURE=false
# Storage
STORAGE_DIR=./storage
PDF_STORAGE_DIR=./storage/pdfs
# Admin Dashboard
ADMIN_ENABLED=true
ADMIN_USER=admin
ADMIN_PASSWORD_HASH=
ADMIN_SESSION_SECRET=change-this-to-a-random-secretnpm testThis runs the automation without starting the server. Great for debugging.
- Start the server:
npm start- Send a POST request:
curl -X POST http://localhost:3000/api/quote \
-H "Content-Type: application/json" \
-d '{
"initials": "M",
"lastName": "Rossi",
"phone": "+39 06 1234567",
"email": "mario.rossi@example.it",
"role": "event_organiser",
"eventName": "Festival Estivo Roma 2026",
"eventType": "18",
"startDate": "2026-06-15",
"days": 3,
"visitors": 500,
"description": "Festival musicale all'aperto",
"venueDescription": "Parco pubblico",
"address": "Via dei Fori Imperiali",
"houseNumber": "1",
"zipcode": "00186",
"city": "Roma",
"country": "it",
"environment": "outdoor",
"coverages": {
"liability": true,
"accident": true
}
}'Submits form data and returns proposal information.
Request Body:
{
"initials": "M",
"lastName": "Rossi",
"phone": "+39 06 1234567",
"email": "mario.rossi@example.it",
"role": "event_organiser",
"roleCompany": "",
"roleVerification": "",
"eventName": "Festival Estivo Roma 2026",
"eventType": "18",
"startDate": "2026-06-15",
"days": 3,
"visitors": 500,
"description": "Festival musicale all'aperto",
"venueDescription": "Parco pubblico",
"address": "Via dei Fori Imperiali",
"houseNumber": "1",
"zipcode": "00186",
"city": "Roma",
"country": "it",
"environment": "outdoor",
"coverages": {
"cancellation_costs": false,
"cancellation_non_appearance": false,
"cancellation_weather": false,
"cancellation_income": false,
"liability": true,
"equipment": false,
"money": false,
"accident": true
}
}Response:
{
"success": true,
"quoteKey": "nr-12345678",
"proposalUrl": "https://verzekeren.norisk.eu/en/agents/product/event-int/proposal?key=...",
"pricing": {
"sumExcl": "100.00",
"policyCosts": "10.00",
"insuranceTax": "11.00",
"total": "121.00"
},
"status": "pending_email",
"htmlContent": "<!DOCTYPE html>...",
"pdfPath": "./storage/pdfs/quote-nr-12345678.pdf",
"message": "Quote submitted. Waiting for PDF email...",
"duration": "25340ms"
}Sends the stored PDF quote to the user's email address.
Request Body:
{
"quoteKey": "nr-12345678"
}Response:
{
"success": true,
"message": "Preventivo inviato con successo via email",
"sentTo": "mario.rossi@example.it",
"duration": "1200ms"
}Checks the status of a quote.
Response:
{
"success": true,
"quoteKey": "nr-12345678",
"status": "completed",
"hasPdf": true,
"createdAt": "2026-03-03T10:30:00.000Z",
"updatedAt": "2026-03-03T10:35:00.000Z"
}Health check endpoint with environment details.
When ADMIN_ENABLED=true:
GET /admin- Admin dashboard (requires login)POST /admin/login- Admin authenticationPOST /admin/logout- Admin logoutGET /admin/api/stats- Dashboard statisticsGET /admin/api/quotes- List all quotes
To run with visible browser window:
# In .env file
HEADLESS=false
SLOW_MO=500
LOG_LEVEL=debugThis will:
- Show the browser window in real-time
- Slow down actions for observation
- Output detailed logs to console
- Take screenshots at each major step
- Session Initialization: Navigate to NoRisk agent login, authenticate
- CSRF Token Extraction: Extract token from the form page
- Step 1 - Personal Details: Fill contact and role information
- Step 2 - Event Details: Fill event type, dates, visitors, description
- Step 3 - Location: Fill venue and address information
- Coverage Selection: Navigate to coverage page, toggle insurance modules
- Proposal Page: Extract pricing information and quote code
- Submission: If
COMPLETED=true, submit proposal and wait for email - PDF Retrieval: Poll IMAP for NoRisk PDF email
- Storage: Save quote to SQLite database
Logs are written to:
- Console: Colored, human-readable format
- logs/automation.log: All log levels
- logs/errors.log: Error-level only
Log levels: error, warn, info, debug
Automatic screenshots are taken at:
- Initial page load
- After form filling
- Coverages page
- After coverage selection
- Proposal page
- Error states
Screenshots saved to: screenshots/
Quotes and their metadata are stored in SQLite:
- Database:
database/quotes.db - PDFs:
storage/pdfs/ - Sessions:
database/sessions.db
The backend expects country codes with a trailing space (e.g., "it ", "nl "), except for "us" and "gb". The dataMapper.js handles this automatically.
Event types must match the IDs expected by NoRisk. See the form page source or browser network tab for valid values.
event_organiser- Default, no additional fields requiredintermediary- RequiresroleCompanyandroleVerificationproxy- RequiresroleCompanyandroleVerification
COMPLETED=false: Stops after extracting quote code (manual review)COMPLETED=true: Submits proposal and waits for PDF email
Generate a bcrypt hash for the admin password:
node -e "console.log(require('bcrypt').hashSync('yourpassword', 10))"- Set
HEADLESS=falseto see what's happening - Check screenshots in
screenshots/folder - Verify selectors in
src/config/constants.js
- Increase
TIMEOUTin.env - Check internet connection
- Verify NoRisk site is accessible
- Check logs for token extraction step
- Verify form URL is correct
- Ensure no CAPTCHA is blocking access
- Verify IMAP credentials
- Check
IMAP_CHECK_INTERVALandIMAP_MAX_WAIT - Ensure NoRisk email is not in spam folder
MIT
Your Name / Organization