cd /opt/bnc/bnc-cpt/bnc-cpt-wui/src/vue/app
npm install
npm run dev # http://localhost:3333
The dev server proxies /api to http://localhost:8100 (API backend).
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
make do-setup-wui # Build and start on port 3333
make do-setup-wui-no-cache # Rebuild from scratch
cd /opt/bnc/bnc-cpt/bnc-cpt-wui
ENV=lcl ./run -a do_build_wui_vue # Install deps + type-check + build
src/vue/app/
├── index.html # HTML entry point
├── vite.config.ts # Vite config (proxy, version injection plugin)
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript config (strict mode)
├── tailwind.config.js # Tailwind theme (CSS variables)
├── .env.dev # Dev API URL (proxied)
├── .env.tst # Test API URL
├── .env.prd # Production API URL
└── src/
├── main.ts # Entry point (Vue + Pinia + I18n)
├── App.vue # Root component (step-based navigation)
├── style.css # Global Tailwind styles
├── components/
│ ├── Header.vue # Navigation + language switcher
│ ├── Hero.vue # Landing page
│ ├── StepIndicator.vue # Progress bar
│ ├── BrandSelector.vue # Vehicle brand selection
│ ├── BrandCarousel.vue # Brand carousel display
│ ├── PaymentStep.vue # Plan + method selection
│ ├── PaymentGateway.vue # Payment processing
│ ├── OAuthStep.vue # Tesla authentication
│ ├── GeneratingStep.vue # Report generation progress
│ ├── ReportDashboard.vue # PDF display + download
│ ├── SupportPage.vue # Support/help articles
│ ├── ServiceAgreement.vue # Terms modal
│ ├── SupportedBrands.vue # Brand grid
│ ├── FeeRow.vue # Price line item
│ └── Footer.vue
├── config.ts # Runtime configuration
├── services/
│ └── api.ts # Centralized HTTP client
├── stores/
│ └── app.ts # Pinia store with localStorage persistence
├── types/
│ └── index.ts # TypeScript interfaces
├── data/
│ └── vehicles.types.ts # Vehicle type data
└── i18n/
├── index.ts # Vue I18n setup
└── locales/
├── en.json # English
├── fi.json # Finnish
├── sv.json # Swedish
├── de.json # German
├── fr.json # French
├── es.json # Spanish
├── it.json # Italian
├── da.json # Danish
└── no.json # Norwegian
Landing → Payment → Payment Gateway → Payment Success → OAuth → Report
Step navigation managed by Pinia store (currentStep). Payment state persisted to localStorage to survive Stripe/PayPal redirects.
All components use <script setup lang="ts">:
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAppStore } from '@/stores/app';
const { t } = useI18n();
const store = useAppStore();
</script>
The Pinia store auto-syncs to localStorage via watchers:
- cpt-payment-id, cpt-selected-plan, cpt-payment-method
- cpt-current-step, cpt-selected-brand-id, cpt-selected-model-id
All backend calls go through services/api.ts:
import { createPaymentIntent, verifyPayment } from '@/services/api';
Base URL from VITE_API_BASE_URL env var. JWT token from localStorage for authenticated requests.
9 languages: English (en), Finnish (fi), Swedish (sv), German (de), French (fr), Spanish (es), Italian (it), Danish (da), Norwegian (no)
{{ t('payment.selectPlan') }}
CSS variable-based theming with:
- Cyan/Indigo gradient theme
- Glass morphism effects (.glass class)
- Responsive mobile-first design (md: breakpoints)
- Custom font families: Inter (base), Playfair Display (display)
npm run dev # Dev server with hot reload (port 3333)
npm run build # Default production build
npm run build:dev # Build for dev environment
npm run build:tst # Build for test environment
npm run build:prd # Build for production environment
npm run type-check # TypeScript validation only
npm run preview # Preview production build
Headless UI tests using Mocha + Puppeteer + Chai, running in the the-bot Docker container.
src/nodejs/the-bot/test/
├── test.config.js # Base config (headless toggle)
├── test.config.bnc.cpt.lcl.js # Local dev (http://cpt-wui:3333)
├── test.config.bnc.cpt.dev.js # Dev (https://dev.carpulsetracker.com)
├── test.config.bnc.cpt.inf.js # Inf (https://inf.carpulsetracker.com)
├── test.config.bnc.cpt.tst.js # Test (https://tst.carpulsetracker.com)
├── test.config.bnc.cpt.prd.js # Production
├── test.utils.js # Helpers: waitForApp, screenshotOnFail, delay
├── test-101.landing-page/ # Page load, version, brand/model select
├── test-102.payment-step/ # Plans, prices, payment methods, terms
├── test-103.language-switcher/ # i18n switching, persistence
├── test-104.i18n-completeness/ # Translation keys validated across locales
├── test-105.user-journey/ # Full landing→brand→plan→payment flow
├── test-106.back-navigation/ # Back button, step indicator clicks, round-trips
├── test-107.support-page/ # Support search, topic/article nav, breadcrumbs
├── test-108.service-agreement/ # Modal open/close, content per locale
├── test-109.error-states/ # Network failures, API errors, graceful degradation
├── test-110.session-persistence/ # Session state persistence across page reloads
├── test-201.payment-wizard/ # Payment flow + Stripe integration
├── test-202.payment-paypal/ # PayPal selection, switching, E2E
├── test-301.tesla-oauth-login/ # Tesla OAuth: login, MFA/TOTP, consent, callback
└── test-302.report-dashboard/ # Multi-vehicle tabs, report sections, PDF download
# From bnc-cpt-utl/
make test-wui # Run headless in the-bot container
./run -a do_run_wui_tests # Same via shell action
# Against specific deployed environment
make test-wui-dev # Against https://dev.carpulsetracker.com
make test-wui-tst # Against https://tst.carpulsetracker.com
Runs Puppeteer directly on your machine (not in Docker). The browser opens visibly so you can watch tests execute.
# From bnc-cpt-utl/
make test-wui-headed # Visible browser, tests against local dev
# Via shell action (more control)
PUPPETEER_HEADLESS=false ./run -a do_run_wui_tests_local
# Against a deployed environment with visible browser
cd /opt/bnc/bnc-cpt/bnc-cpt-wui/src/nodejs/the-bot
PUPPETEER_HEADLESS=false ORG=bnc APP=cpt ENV=dev npm test
Prerequisites for headed mode on host:
- Node.js 18+ installed on host
- Chrome/Chromium installed on host (Puppeteer downloads its own if not set)
- The target URL must be reachable from your machine
- For lcl: the WUI dev server must be running on localhost:3333
This runs Chromium inside the Docker container but displays the browser window on your host's screen. Useful when you want the container's exact environment but need visual feedback.
# 1. Allow Docker to access your X11 display
xhost +local:docker
# 2. Start the-bot with X11 forwarding
docker run --rm -it \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
-v /opt/bnc/bnc-cpt:/opt/bnc/bnc-cpt \
-e PUPPETEER_HEADLESS=false \
-e PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \
--network all_lcl_docker \
img-the-bot bash -c '
cd /opt/bnc/bnc-cpt/bnc-cpt-wui/src/nodejs/the-bot
ORG=bnc APP=cpt ENV=lcl npm test
'
# 3. Revoke X11 access when done
xhost -local:docker
Requirements:
- Linux host with X11 (not Wayland — for Wayland use XDG_RUNTIME_DIR)
- xhost command available (apt install x11-xserver-utils)
- Container image img-the-bot must be built (make do-setup-wui)
Limitations:
- Linux only — does not work on macOS or Windows (WSL2 needs extra setup)
- Security: xhost +local:docker grants display access to all local Docker containers
- The container's Chromium may render slightly differently than your host's Chrome
All test configs read PUPPETEER_HEADLESS from the environment:
// test.config.js
const puppeteer = {
headless: process.env.PUPPETEER_HEADLESS !== 'false', // true by default
args: ["--no-sandbox", "--disable-setuid-sandbox"],
};
Set PUPPETEER_HEADLESS=false to open a visible browser. Any other value (or unset) = headless.
dat/test-report/report.html (Mochawesome interactive report)dat/test-report/report.json (machine-readable)dat/test-report/screenshots/ (auto-captured on failure)# Shell into the-bot container
make test-wui-shell
# Then run tests manually
cd /opt/bnc/bnc-cpt/bnc-cpt-wui/src/nodejs/the-bot
ORG=bnc APP=cpt ENV=lcl npm test
# Run a single test file
npx mocha --timeout 30000 test/test-101.landing-page/test-101.landing-page.spec.js
# View container logs
make test-wui-logs
The app version is derived from git tags and injected at build time as an HTML meta tag.
v0.2.3) is the source of truthVITE_APP_VERSION env varversionInjectionPlugin in vite.config.ts) injects:
html
<meta name="version" content="v0.2.3" />cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Bump patch: v0.2.3 -> v0.2.4
./run -a do_version_bump
# Bump minor: v0.2.3 -> v0.3.0
BUMP=minor ./run -a do_version_bump
# Push the tag
git -C /opt/bnc/bnc-cpt/bnc-cpt-wui push origin v0.2.4
Increment logic: patch 0-9 then minor rolls, minor 0-9 then major rolls.
curl -s https://dev.carpulsetracker.com/index.html | grep 'meta name="version"'
# <meta name="version" content="v0.2.3" />
ci.yaml)Single workflow with 4 jobs:
do_build_wui_vue shell action (npm install + type-check + build). Runs UI tests against local dev server.do_gcp_deploy_wuiAuto-deploy on master push: inf + dev. Manual deploy via workflow_dispatch: any env.
After deployment, the post-deploy-test job:
- Fetches test credentials (Stripe test card, PayPal test user) from GCP Secret Manager
- Builds the-bot container with Puppeteer
- Runs full UI test suite against the deployed environment URL (e.g., https://dev.carpulsetracker.com)
- Passes secrets via docker exec -e (in-memory only, masked in CI logs)
- Uploads Mochawesome HTML report as a downloadable artifact
- Skips prd environment (production is never auto-tested)
# Manual deploy to tst
gh workflow run ci.yaml -f environment=tst --repo csitea/bnc-cpt-wui
# Monitor
gh run list --repo csitea/bnc-cpt-wui
VITE_APP_VERSION from git taggsutil rsync dist/ to gs://bnc-cpt-{env}-site-statichttps://{fqdn}/index.html| Env | Site URL | API URL |
|---|---|---|
| dev | https://dev.carpulsetracker.com | https://dev.api.carpulsetracker.com/api/v1 |
| tst | https://tst.carpulsetracker.com | https://tst.api.carpulsetracker.com/api/v1 |
| prd | https://carpulsetracker.com | https://api.carpulsetracker.com/api/v1 |
| inf | https://inf.carpulsetracker.com | https://inf.api.carpulsetracker.com/api/v1 |
src/components/NewComponent.vue with <script setup lang="ts">App.vue or parent componenti18n/locales/ (en, fi, sv, de, fr, es, it, da, no)@/ alias for imports from src/