413 lines
22 KiB
JavaScript
413 lines
22 KiB
JavaScript
const fs = require('fs')
|
|
const path = require('path')
|
|
const WebSocket = require('ws')
|
|
|
|
const outputDir = path.resolve(__dirname, 'full-screenshots')
|
|
const pageFilter = new Set(String(process.env.MES_MANUAL_PAGE_FILTER || '').split('|').filter(Boolean))
|
|
const manifestPath = path.join(outputDir, pageFilter.size ? 'manifest-recapture.json' : 'manifest.json')
|
|
const baseUrl = process.env.MES_MANUAL_BASE_URL || 'https://127.0.0.1:1998'
|
|
const debugPort = process.env.MES_MANUAL_DEBUG_PORT || '9223'
|
|
const captureDelay = Number(process.env.MES_MANUAL_CAPTURE_DELAY || 5000)
|
|
const loginUser = process.env.MES_MANUAL_USER
|
|
const loginPassword = process.env.MES_MANUAL_PASSWORD
|
|
const loginRole = process.env.MES_MANUAL_ROLE
|
|
const loginPersonnelId = process.env.MES_MANUAL_PERSONNEL_ID || '1'
|
|
const loginName = process.env.MES_MANUAL_PERSONNEL_NAME || '超级管理员'
|
|
const loginAccount = process.env.MES_MANUAL_ACCOUNT || '0000'
|
|
|
|
fs.mkdirSync(outputDir, { recursive: true })
|
|
|
|
const modules = [
|
|
{
|
|
key: 'device',
|
|
title: '设备管理',
|
|
pages: [
|
|
['设备信息维护', '/DeviceManagement/DeviceInformation/index', 'DeviceManagement/DeviceInformation/index.vue'],
|
|
['设备数据', '/DeviceManagement/Devicedata/index', 'DeviceManagement/Devicedata/index.vue'],
|
|
['设备历史数据', '/DeviceManagement/DeviceDataHistory/index', 'DeviceManagement/DeviceDataHistory/index.vue'],
|
|
['实时数据', '/DeviceManagement/DeviceRealData/index', 'DeviceManagement/DeviceRealData/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'research',
|
|
title: '研发管理',
|
|
pages: [
|
|
['设计任务', '/ResearchManagement/DesignReportTask/Task/index', 'ProductionManagement/DesignReportTask/Task/index.vue'],
|
|
['设计报工', '/ResearchManagement/DesignReportTask/Report/index', 'ProductionManagement/DesignReportTask/Report/index.vue'],
|
|
['设计工时校验', '/ResearchManagement/DesignReportTask/Check/index', 'ProductionManagement/DesignReportTask/Check/index.vue'],
|
|
['设计工时报表', '/ResearchManagement/DesignReportTask/HoursReport/index', 'ProductionManagement/DesignReportTask/HoursReport/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'process',
|
|
title: '工艺管理',
|
|
pages: [
|
|
['工序管理', '/ProcessManagement/ProcedureManagement/index', 'ProcessManagement/ProcedureManagement/index.vue'],
|
|
['制定标准工艺', '/ProcessManagement/ProcessesDevelop/index', 'ProcessManagement/ProcessesDevelop/index.vue'],
|
|
['生产工艺', '/ProcessManagement/ProcessProduct/index', 'ProcessManagement/ProcessProduct/index.vue'],
|
|
['工艺查询', '/ProcessManagement/ProcessInquiry/index', 'ProcessManagement/ProcessInquiry/index.vue'],
|
|
['工艺任务', '/ProcessManagement/ProductionOrder/index', 'ProcessManagement/ProductionOrder/index.vue'],
|
|
['图纸维护', '/ProcessManagement/PartDrawing/index', 'ProcessManagement/PartDrawing/index.vue'],
|
|
['工艺维护', '/ProcessManagement/ProcessDrawing/index', 'ProcessManagement/ProcessDrawing/index.vue'],
|
|
['查看图纸', '/ProcessManagement/PartDrawlook/index', 'ProcessManagement/PartDrawlook/index.vue'],
|
|
['采购图纸查询', '/ProcessManagement/ProcurementDrawlook/index', 'ProcessManagement/ProcurementDrawlook/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'plan',
|
|
title: '计划排产',
|
|
pages: [
|
|
['机加件排产', '/PlanManagement/SelfMakePlan/index', 'PlanManagement/SelfMakePlan/index.vue'],
|
|
['装配件排产', '/PlanManagement/InstallMakePlan/index', 'PlanManagement/InstallMakePlan/index.vue'],
|
|
['机加件已排产', '/PlanManagement/SelfMakePlandone/index', 'PlanManagement/SelfMakePlandone/index.vue'],
|
|
['机加件未排产', '/PlanManagement/SelfMakePlando/index', 'PlanManagement/SelfMakePlando/index.vue'],
|
|
['优先装配', '/PlanManagement/priorityinstall/index', 'PlanManagement/priorityinstall/index.vue'],
|
|
['计划用表', '/PlanManagement/PlanShell/index', 'PlanManagement/PlanShell/index.vue'],
|
|
['装配未排产', '/PlanManagement/InstallMakePlanDo/index', 'PlanManagement/InstallMakePlanDo/index.vue'],
|
|
['生产订单关闭', '/PlanManagement/PlannOrderClose/index', 'PlanManagement/PlannOrderClose/index.vue'],
|
|
['工位任务汇总', '/PlanManagement/WorkstationPlan/index', 'PlanManagement/WorkstationPlan/index.vue'],
|
|
['装配已排产', '/PlanManagement/InstallMakePlanDone/index', 'PlanManagement/InstallMakePlanDone/index.vue'],
|
|
['系统类装配任务', '/PlanManagement/SystemInstallTask/index', 'PlanManagement/SystemInstallTask/index.vue'],
|
|
['阀类装配任务', '/PlanManagement/ValueInstallTask/index', 'PlanManagement/ValueInstallTask/index.vue'],
|
|
['及时齐套跟踪结果', '/PlanManagement/TimelyKitTrackingResult/index', 'PlanManagement/TimelyKitTrackingResult/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'production',
|
|
title: '生产管理',
|
|
pages: [
|
|
['生产计划跟踪', '/ProductionManagement/ProductionPlanTrack/index', 'ProductionManagement/ProductionPlanTrack/index.vue'],
|
|
['加工中心', '/ProductionManagement/MachiningCenter/index', 'ProductionManagement/MachiningCenter/index.vue'],
|
|
['过程异常管控', '/ProductionManagement/AbnormalControl/index', 'ProductionManagement/AbnormalControl/index.vue'],
|
|
['工时校验', '/ProductionManagement/WorkHoursCheck/index', 'ProductionManagement/WorkHoursCheck/index.vue'],
|
|
['外协管理', '/ProductionManagement/OutsourceMange/index', 'ProductionManagement/OutsourceMange/index.vue'],
|
|
['人员绑定', '/ProductionManagement/PersonnelBinding/index', 'ProductionManagement/PersonnelBinding/index.vue'],
|
|
['装配中心', '/ProductionManagement/AssemblyCenter/index', 'ProductionManagement/AssemblyCenter/index.vue'],
|
|
['班次管理', '/ProductionManagement/ShiftManagement/index', 'ProductionManagement/ShiftManagement/index.vue'],
|
|
['工时报表', '/ProductionManagement/Timesheet/index', 'ProductionManagement/Timesheet/index.vue'],
|
|
['加急件管理', '/ProductionManagement/Urgent/index', 'ProductionManagement/Urgent/index.vue'],
|
|
['拣配任务', '/ProductionManagement/PickingTask/index', 'ProductionManagement/PickingTask/index.vue'],
|
|
['采购外协管理', '/ProductionManagement/SaleOutsourceMange/index', 'ProductionManagement/SaleOutsourceMange/index.vue'],
|
|
['工时查询', '/ProductionManagement/Workhours/index', 'ProductionManagement/Workhours/index.vue'],
|
|
['机加正在进行中', '/ProductionManagement/DeviceStatus/index', 'ProductionManagement/DeviceStatus/index.vue'],
|
|
['装配状态', '/ProductionManagement/AssembleStatus/index', 'ProductionManagement/AssembleStatus/index.vue'],
|
|
['外协报表', '/ProductionManagement/OutsourceSheet/index', 'ProductionManagement/OutsourceSheet/index.vue'],
|
|
['工时修正', '/ProductionManagement/WorkhoursEdit/index', 'ProductionManagement/WorkhoursEdit/index.vue'],
|
|
['物料机器人', '/ProductionManagement/AGVtask/index', 'ProductionManagement/AGVtask/index.vue'],
|
|
['手续确认报表', '/ProductionManagement/FormalityConfirmReport/index', 'ProductionManagement/FormalityConfirmReport/index.vue'],
|
|
['发货通知列表', '/ProductionManagement/DeliveryNoticeList/index', 'ProductionManagement/DeliveryNoticeList/index.vue'],
|
|
['结算工时', '/ProductionManagement/SettlementWorkhours/index', 'ProductionManagement/SettlementWorkhours/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'anomaly',
|
|
title: '异常提醒',
|
|
pages: [
|
|
['机加及时开工', '/AnomalousManagement/PunctualWork/index', 'AnomalousManagement/PunctualWork/index.vue'],
|
|
['外协及时开工', '/AnomalousManagement/OutPunctualWork/index', 'AnomalousManagement/OutPunctualWork/index.vue'],
|
|
['加急任务列表', '/AnomalousManagement/UrgentTask/index', 'AnomalousManagement/UrgentTask/index.vue'],
|
|
['装配及时送检', '/AnomalousManagement/AssemblyPunctualInspection/index', 'AnomalousManagement/AssemblyPunctualInspection/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'quality',
|
|
title: '质量管理',
|
|
pages: [
|
|
['终检', '/QualityManagement/CheckTask/final', 'QualityManagement/CheckTask/final.vue'],
|
|
['采购入库检', '/QualityManagement/CheckTask/purchaseIncoming', 'QualityManagement/CheckTask/purchaseIncoming.vue'],
|
|
['销售退货检', '/QualityManagement/CheckTask/salesReturn', 'QualityManagement/CheckTask/salesReturn.vue'],
|
|
['原材料退库检', '/QualityManagement/CheckTask/materialReturn', 'QualityManagement/CheckTask/materialReturn.vue'],
|
|
['序检收检', '/QualityManagement/CheckTask/processReceive', 'QualityManagement/CheckTask/processReceive.vue'],
|
|
['质量检验标准', '/QualityManagement/QualityStandardQuery/index', 'QualityManagement/QualityStandardQuery/index.vue'],
|
|
['其他入库检', '/QualityManagement/CheckTask/OtherInbound', 'QualityManagement/CheckTask/OtherInbound.vue'],
|
|
['质检明细', '/QualityManagement/QualityDetails/index', 'QualityManagement/QualityDetails/index.vue'],
|
|
['不合格品处置', '/QualityManagement/Unqualified/index', 'QualityManagement/Unqualified/index.vue'],
|
|
['质检报工', '/QualityManagement/QualityCenter/index', 'QualityManagement/QualityCenter/index.vue'],
|
|
['测试装配任务', '/QualityManagement/TestAssemblyTask/index', 'QualityManagement/TestAssemblyTask/index.vue'],
|
|
['及时检验报表', '/QualityManagement/CheckTask/punctualInspection', 'QualityManagement/CheckTask/punctualInspection.vue'],
|
|
['产品合格率(工位)', '/QualityManagement/ProductPassRateWorkstation/index', 'ProductionManagement/ProductPassRateWorkstation/index.vue']
|
|
]
|
|
},
|
|
{
|
|
key: 'sales',
|
|
title: '销售订单管理',
|
|
pages: [
|
|
['销售订单状态', '/SaleManagement/saleorderstatus/index', 'SaleManagement/saleorderstatus/index.vue']
|
|
]
|
|
}
|
|
]
|
|
|
|
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
|
|
async function connect() {
|
|
const targets = await fetch(`http://127.0.0.1:${debugPort}/json`).then(res => res.json())
|
|
const target = targets.find(item => item.type === 'page')
|
|
if (!target) throw new Error('No Chrome page target found')
|
|
|
|
const ws = new WebSocket(target.webSocketDebuggerUrl)
|
|
let id = 0
|
|
const pending = new Map()
|
|
ws.on('message', raw => {
|
|
const message = JSON.parse(raw.toString())
|
|
if (message.id && pending.has(message.id)) {
|
|
const request = pending.get(message.id)
|
|
pending.delete(message.id)
|
|
message.error ? request.reject(new Error(message.error.message)) : request.resolve(message.result)
|
|
}
|
|
})
|
|
await new Promise((resolve, reject) => {
|
|
ws.once('open', resolve)
|
|
ws.once('error', reject)
|
|
})
|
|
|
|
const send = (method, params = {}) => new Promise((resolve, reject) => {
|
|
const requestId = ++id
|
|
pending.set(requestId, { resolve, reject })
|
|
ws.send(JSON.stringify({ id: requestId, method, params }))
|
|
})
|
|
return { ws, send }
|
|
}
|
|
|
|
async function main() {
|
|
const { ws, send } = await connect()
|
|
await send('Page.enable')
|
|
await send('Runtime.enable')
|
|
await send('Network.enable')
|
|
await send('Security.enable')
|
|
await send('Security.setIgnoreCertificateErrors', { ignore: true })
|
|
await send('Emulation.setDeviceMetricsOverride', {
|
|
width: 1920,
|
|
height: 1080,
|
|
deviceScaleFactor: 1,
|
|
mobile: false
|
|
})
|
|
|
|
const evaluate = expression => send('Runtime.evaluate', {
|
|
expression,
|
|
awaitPromise: true,
|
|
returnByValue: true
|
|
}).then(result => result.result.value)
|
|
|
|
async function waitFor(expression, timeout = 45000) {
|
|
const started = Date.now()
|
|
while (Date.now() - started < timeout) {
|
|
if (await evaluate(`Boolean(${expression})`)) return true
|
|
await wait(300)
|
|
}
|
|
return false
|
|
}
|
|
|
|
async function clickByText(text, selector = 'button') {
|
|
return evaluate(`(() => {
|
|
const normalize = value => String(value || '').replace(/\\s+/g, '')
|
|
const node = [...document.querySelectorAll(${JSON.stringify(selector)})]
|
|
.find(item => item.offsetParent !== null && !item.disabled &&
|
|
normalize(item.innerText).includes(${JSON.stringify(text.replace(/\s+/g, ''))}))
|
|
if (!node) return false
|
|
node.click()
|
|
return true
|
|
})()`)
|
|
}
|
|
|
|
async function login() {
|
|
if (loginRole) {
|
|
for (const [name, value] of [
|
|
['Admin-Token', loginRole],
|
|
['id', loginPersonnelId],
|
|
['name', loginName],
|
|
['account', loginAccount]
|
|
]) {
|
|
await send('Network.setCookie', { name, value, url: baseUrl })
|
|
}
|
|
await send('Page.navigate', { url: `${baseUrl}/#/` })
|
|
const ready = await waitFor("document.querySelector('.sidebar-container')", 60000)
|
|
if (!ready) throw new Error('Cookie session did not reach the application shell')
|
|
return
|
|
}
|
|
await send('Page.navigate', { url: `${baseUrl}/#/login` })
|
|
await waitFor("document.querySelector('input[name=username]') || document.querySelector('.sidebar-container')")
|
|
if (await evaluate("Boolean(document.querySelector('input[name=username]'))")) {
|
|
if (!loginUser || !loginPassword) throw new Error('MES manual login credentials are not available')
|
|
await evaluate(`(() => {
|
|
const setValue = (element, value) => {
|
|
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set
|
|
setter.call(element, value)
|
|
element.dispatchEvent(new Event('input', { bubbles: true }))
|
|
element.dispatchEvent(new Event('change', { bubbles: true }))
|
|
}
|
|
setValue(document.querySelector('input[name=username]'), ${JSON.stringify(loginUser)})
|
|
setValue(document.querySelector('input[name=password]'), ${JSON.stringify(loginPassword)})
|
|
})()`)
|
|
await clickByText('登录')
|
|
await wait(1200)
|
|
if (await evaluate("Boolean(document.querySelector('.el-dialog__wrapper:not([style*=\"display: none\"]) .el-table__row'))")) {
|
|
await evaluate("document.querySelector('.el-dialog__wrapper:not([style*=\"display: none\"]) .el-table__row').click()")
|
|
await clickByText('确定', '.el-dialog__wrapper:not([style*="display: none"]) button')
|
|
}
|
|
}
|
|
const ready = await waitFor("document.querySelector('.sidebar-container')", 60000)
|
|
if (!ready) throw new Error('Login did not reach the application shell')
|
|
}
|
|
|
|
async function capture(page, fileName) {
|
|
await send('Page.navigate', { url: `${baseUrl}/#${page.route}` })
|
|
const ready = await waitFor("document.readyState === 'complete' && (document.querySelector('.app-main') || document.querySelector('.app-container'))")
|
|
if (!ready) throw new Error(`Page shell timed out: ${page.route}`)
|
|
await waitFor("!document.querySelector('.el-loading-mask:not([style*=\"display: none\"])')", 15000)
|
|
if (page.title === '生产工艺') {
|
|
await waitFor(`(() => {
|
|
const app = document.querySelector('#app') && document.querySelector('#app').__vue__
|
|
return Boolean(app && app.$route && app.$route.matched && app.$route.matched
|
|
.some(record => Object.values(record.instances || {}).some(instance => instance && typeof instance.searchTable === 'function')))
|
|
})()`, 15000)
|
|
const filtered = await evaluate(`(() => {
|
|
const app = document.querySelector('#app') && document.querySelector('#app').__vue__
|
|
const records = app && app.$route && app.$route.matched ? [...app.$route.matched].reverse() : []
|
|
for (const record of records) {
|
|
const instances = Object.values(record.instances || {}).filter(Boolean)
|
|
const vm = instances.find(instance => instance.taskstatus !== undefined && typeof instance.searchTable === 'function')
|
|
if (!vm) continue
|
|
vm.taskstatus = '2'
|
|
vm.searchTable()
|
|
return true
|
|
}
|
|
return false
|
|
})()`)
|
|
if (!filtered) throw new Error('生产工艺页面路由实例未就绪')
|
|
} else {
|
|
await clickByText('查询')
|
|
}
|
|
await waitFor("!document.querySelector('.el-loading-mask:not([style*=\"display: none\"])')", 30000)
|
|
if (page.title === '机加件已排产') {
|
|
await waitFor("document.querySelectorAll('.el-table__body-wrapper .el-table__row').length > 0", 60000)
|
|
}
|
|
await wait(captureDelay)
|
|
await evaluate('window.scrollTo(0, 0)')
|
|
const result = await send('Page.captureScreenshot', {
|
|
format: 'png',
|
|
captureBeyondViewport: false,
|
|
fromSurface: true
|
|
})
|
|
fs.writeFileSync(path.join(outputDir, fileName), Buffer.from(result.data, 'base64'))
|
|
return evaluate(`({
|
|
title: document.title,
|
|
bodyTextLength: document.body ? document.body.innerText.length : 0,
|
|
hasError: Boolean(document.querySelector('.el-message--error')),
|
|
route: location.hash
|
|
})`)
|
|
}
|
|
|
|
async function captureEditDialog(page) {
|
|
const dialogConfig = {
|
|
'机加件已排产': {
|
|
button: '编辑',
|
|
title: '编辑备注信息',
|
|
fileName: '04-03-plan-edit-dialog.png'
|
|
},
|
|
'生产工艺': {
|
|
button: '编辑工艺',
|
|
title: '编辑生产工艺',
|
|
fileName: '03-03-process-edit-dialog.png'
|
|
}
|
|
}[page.title]
|
|
if (!dialogConfig) return null
|
|
let opened = await evaluate(`(() => {
|
|
const normalize = value => String(value || '').replace(/\\s+/g, '')
|
|
const button = [...document.querySelectorAll('button')]
|
|
.find(item => normalize(item.innerText) === '${dialogConfig.button}')
|
|
if (!button) return false
|
|
button.click()
|
|
return true
|
|
})()`)
|
|
if (!opened) throw new Error(`${page.title}页面未找到可用的${dialogConfig.button}按钮`)
|
|
const dialogReady = await waitFor(`[...document.querySelectorAll('.el-dialog__wrapper:not([style*="display: none"])')]
|
|
.some(item => String(item.innerText || '').includes('${dialogConfig.title}'))`, 15000)
|
|
if (!dialogReady) throw new Error(`${dialogConfig.title}弹窗未打开`)
|
|
if (page.title === '生产工艺') {
|
|
let populated = await waitFor("document.querySelectorAll('.el-dialog__wrapper:not([style*=\"display: none\"]) .el-table__body-wrapper .el-table__row').length > 0", 5000)
|
|
const buttonCount = await evaluate(`(() => {
|
|
const normalize = value => String(value || '').replace(/\\s+/g, '')
|
|
return [...document.querySelectorAll('button')]
|
|
.filter(item => item.offsetParent !== null && normalize(item.innerText) === '${dialogConfig.button}').length
|
|
})()`)
|
|
for (let index = 1; !populated && index < buttonCount; index += 1) {
|
|
await evaluate(`(() => {
|
|
const dialog = [...document.querySelectorAll('.el-dialog__wrapper:not([style*=\"display: none\"])')]
|
|
.find(item => String(item.innerText || '').includes('${dialogConfig.title}'))
|
|
const close = dialog && dialog.querySelector('.el-dialog__headerbtn')
|
|
if (close) close.click()
|
|
})()`)
|
|
await waitFor(`![...document.querySelectorAll('.el-dialog__wrapper:not([style*=\"display: none\"])')]
|
|
.some(item => String(item.innerText || '').includes('${dialogConfig.title}'))`, 5000)
|
|
opened = await evaluate(`(() => {
|
|
const normalize = value => String(value || '').replace(/\\s+/g, '')
|
|
const buttons = [...document.querySelectorAll('button')]
|
|
.filter(item => item.offsetParent !== null && normalize(item.innerText) === '${dialogConfig.button}')
|
|
if (!buttons[${index}]) return false
|
|
buttons[${index}].click()
|
|
return true
|
|
})()`)
|
|
if (!opened) continue
|
|
await waitFor(`[...document.querySelectorAll('.el-dialog__wrapper:not([style*=\"display: none\"])')]
|
|
.some(item => String(item.innerText || '').includes('${dialogConfig.title}'))`, 5000)
|
|
populated = await waitFor("document.querySelectorAll('.el-dialog__wrapper:not([style*=\"display: none\"]) .el-table__body-wrapper .el-table__row').length > 0", 5000)
|
|
}
|
|
}
|
|
await wait(1000)
|
|
const result = await send('Page.captureScreenshot', {
|
|
format: 'png',
|
|
captureBeyondViewport: false,
|
|
fromSurface: true
|
|
})
|
|
fs.writeFileSync(path.join(outputDir, dialogConfig.fileName), Buffer.from(result.data, 'base64'))
|
|
return { fileName: dialogConfig.fileName, title: dialogConfig.title }
|
|
}
|
|
|
|
await login()
|
|
const manifest = []
|
|
const totalPages = modules.reduce((total, module) => {
|
|
return total + module.pages.filter(page => pageFilter.size === 0 || pageFilter.has(page[0])).length
|
|
}, 0)
|
|
let sequence = 0
|
|
for (let moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) {
|
|
const module = modules[moduleIndex]
|
|
for (let pageIndex = 0; pageIndex < module.pages.length; pageIndex++) {
|
|
sequence++
|
|
const [title, route, source] = module.pages[pageIndex]
|
|
if (pageFilter.size && !pageFilter.has(title)) {
|
|
sequence--
|
|
continue
|
|
}
|
|
const fileName = `${String(moduleIndex + 1).padStart(2, '0')}-${String(pageIndex + 1).padStart(2, '0')}-${module.key}.png`
|
|
const item = { sequence, module: module.title, title, route, source, fileName, status: 'pending' }
|
|
try {
|
|
item.pageState = await capture(item, fileName)
|
|
item.dialogCapture = await captureEditDialog(item)
|
|
item.status = 'captured'
|
|
console.log(`[${sequence}/${totalPages}] ${module.title} / ${title}`)
|
|
} catch (error) {
|
|
item.status = 'failed'
|
|
item.error = error.message
|
|
console.error(`[${sequence}/${totalPages}] FAILED ${module.title} / ${title}: ${error.message}`)
|
|
}
|
|
manifest.push(item)
|
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
}
|
|
}
|
|
ws.close()
|
|
|
|
const failed = manifest.filter(item => item.status !== 'captured')
|
|
console.log(`Captured ${manifest.length - failed.length}/${manifest.length} pages`)
|
|
await send('Browser.close')
|
|
if (failed.length) process.exitCode = 1
|
|
}
|
|
|
|
module.exports = { modules }
|
|
|
|
if (require.main === module) {
|
|
main().catch(error => {
|
|
console.error(error.stack || error.message)
|
|
process.exitCode = 1
|
|
})
|
|
}
|