HR-ATS-Portal/frontend/mobile.test.mjs

109 lines
4.4 KiB
JavaScript

/* ============================================================
mobile.test.mjs — mobile-viewport regression test.
Opens every app route at phone/tablet widths against the running dev
server, logs in as the local seeded tester, and fails if any route lets
the main pane scroll sideways (the classic clipped-card / blown-out-grid
symptom) or throws a runtime error. Intended horizontal scrollers
(.table-wrap, .kanban, .tabs, .stepper) are exempt by design: they scroll
inside themselves, never the pane.
Requirements (not part of the default `npm run verify` chain):
- dev server running (npm run dev) and the backend reachable
- Chrome installed (CHROME_PATH overrides the default location)
- puppeteer-core available: npm i -D puppeteer-core (no download;
it drives the installed Chrome)
Run: node mobile.test.mjs
Env: ATS_BASE_URL, CHROME_PATH, ATS_TEST_EMAIL, ATS_TEST_PASSWORD
============================================================ */
let puppeteer
try {
puppeteer = (await import('puppeteer-core')).default
} catch {
console.error('puppeteer-core is not installed. Run: npm i -D puppeteer-core')
process.exit(2)
}
const BASE = process.env.ATS_BASE_URL || 'http://localhost:5173'
const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
const EMAIL = process.env.ATS_TEST_EMAIL || 'ats.tester@example.com'
const PASSWORD = process.env.ATS_TEST_PASSWORD || 'Test12345!'
// Keep in sync with src/app/routes.js (paths only — titles don't matter here).
const ROUTES = [
'dashboard', 'inbox', 'matching', 'jobs', 'candidates', 'talentpool', 'pipeline',
'progress', 'import', 'jobboard', 'recruiterhub', 'talent', 'tasks', 'aiassistant',
'interviews', 'requisitions', 'assessments', 'offers', 'managers', 'calendar',
'reports', 'analytics', 'aistudio', 'notifications', 'rbac', 'settings', 'help',
]
const WIDTHS = [320, 375, 390, 430, 768]
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const browser = await puppeteer.launch({
executablePath: CHROME,
headless: true,
args: ['--no-sandbox'],
defaultViewport: { width: 320, height: 800, isMobile: true, hasTouch: true },
})
const failures = []
const runtimeErrors = []
try {
const page = await browser.newPage()
page.on('pageerror', (e) => runtimeErrors.push(`${page.url()}: ${e.message.split('\n')[0]}`))
await page.goto(`${BASE}/auth/login`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await sleep(1000)
if (await page.$('input[type=password]')) {
const email = await page.$('input:not([type=password])')
await email.type(EMAIL)
await page.type('input[type=password]', PASSWORD)
await Promise.all([
page.click('button[type=submit]'),
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {}),
])
await sleep(1500)
}
for (const route of ROUTES) {
for (const w of WIDTHS) {
await page.setViewport({ width: w, height: 800, isMobile: w < 768, hasTouch: w < 768 })
await page.goto(`${BASE}/${route}`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await sleep(1500)
let rep
try {
rep = await page.evaluate(() => {
const doc = document.documentElement
const content = document.querySelector('.content')
return {
docOverflow: doc.scrollWidth - doc.clientWidth,
contentOverflow: content ? content.scrollWidth - content.clientWidth : 0,
}
})
} catch {
await sleep(1500)
continue // page navigated mid-evaluate; the next loop iteration re-covers it
}
if (rep.docOverflow > 1) failures.push(`${route} @${w}: document scrolls sideways by ${rep.docOverflow}px`)
if (rep.contentOverflow > 1) failures.push(`${route} @${w}: .content scrolls sideways by ${rep.contentOverflow}px`)
}
process.stdout.write('.')
}
console.log('')
} finally {
await browser.close()
}
// 4xx/5xx fetch noise is backend behaviour, not a layout bug — only genuine
// script errors (pageerror) fail the run.
if (failures.length || runtimeErrors.length) {
for (const f of failures) console.error('FAIL ' + f)
for (const e of runtimeErrors) console.error('ERROR ' + e)
console.error(`\n${failures.length} overflow failure(s), ${runtimeErrors.length} runtime error(s)`)
process.exit(1)
}
console.log(`All ${ROUTES.length} routes clean at ${WIDTHS.join('/')}px — no sideways scroll, no runtime errors`)