109 lines
4.4 KiB
JavaScript
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`)
|