Review changes batch 5 of 6
This commit is contained in:
@@ -6,7 +6,7 @@ const renderSettleMilliseconds = 900
|
||||
const chromeDebuggingPort = process.env.CHROME_DEBUGGING_PORT || '9222'
|
||||
|
||||
if (!url || !selector || !output) {
|
||||
throw new Error('Usage: node scripts/capture-chrome-page.js <url> <selector> <output> [width] [height] [password-tab|sms-tab|invalid-login|verification-dialog|g06-results|g06-empty|g06-invite|fresh-navigation]')
|
||||
throw new Error('Usage: node scripts/capture-chrome-page.js <url> <selector> <output> [width] [height] [password-tab|sms-tab|invalid-login|verification-dialog|g06-results|g06-empty|g06-invite|g03-*|fresh-navigation]')
|
||||
}
|
||||
|
||||
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||
@@ -149,9 +149,9 @@ const waitForFreshDocument = async (send, documentTimeOrigin) => {
|
||||
}
|
||||
|
||||
const prepareG06State = async (send, action) => {
|
||||
if (action !== 'g06-results' && action !== 'g06-empty' && action !== 'g06-invite') return
|
||||
if (action !== 'g06-results' && action !== 'g06-empty' && action !== 'g06-error' && action !== 'g06-invite' && action !== 'g06-invite-invalid') return
|
||||
|
||||
if (action === 'g06-invite') {
|
||||
if (action === 'g06-invite' || action === 'g06-invite-invalid') {
|
||||
const switched = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const tab = document.querySelectorAll('.mode-tab')[1]
|
||||
@@ -168,7 +168,7 @@ const prepareG06State = async (send, action) => {
|
||||
const input = document.querySelector('.invite-input input')
|
||||
const button = document.querySelector('.invite-controls .search-action')
|
||||
if (!input || !button) return false
|
||||
input.value = 'JP2026'
|
||||
input.value = ${JSON.stringify(action === 'g06-invite' ? 'JP2026' : 'INVALID')}
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
return true
|
||||
})()`,
|
||||
@@ -179,7 +179,7 @@ const prepareG06State = async (send, action) => {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.invite-controls .search-action').click()" })
|
||||
} else {
|
||||
|
||||
const keyword = action === 'g06-results' ? '汤' : '不存在的家谱'
|
||||
const keyword = action === 'g06-results' ? '汤' : action === 'g06-error' ? '失败' : '不存在的家谱'
|
||||
const prepared = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const input = document.querySelector('.search-input input')
|
||||
@@ -195,7 +195,7 @@ const prepareG06State = async (send, action) => {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.search-action').click()" })
|
||||
}
|
||||
|
||||
const stateSelector = action === 'g06-results' ? '.search-results .genealogy-card' : action === 'g06-empty' ? '.search-empty' : '.invite-result .genealogy-card'
|
||||
const stateSelector = action === 'g06-results' ? '.search-results .genealogy-card' : action === 'g06-empty' ? '.search-empty' : action === 'g06-error' ? '.search-error' : action === 'g06-invite-invalid' ? '.invite-invalid' : '.invite-result .genealogy-card'
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
const result = await send('Runtime.evaluate', {
|
||||
expression: `Boolean(document.querySelector(${JSON.stringify(stateSelector)}))`,
|
||||
@@ -207,6 +207,267 @@ const prepareG06State = async (send, action) => {
|
||||
}
|
||||
}
|
||||
|
||||
const waitForPageLoad = async (socket, trigger) => new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
socket.removeEventListener('message', onMessage)
|
||||
reject(new Error('Page reload did not emit Page.loadEventFired'))
|
||||
}, 4000)
|
||||
const onMessage = (event) => {
|
||||
const message = JSON.parse(event.data)
|
||||
if (message.method !== 'Page.loadEventFired') return
|
||||
clearTimeout(timeout)
|
||||
socket.removeEventListener('message', onMessage)
|
||||
resolve()
|
||||
}
|
||||
socket.addEventListener('message', onMessage)
|
||||
Promise.resolve().then(trigger).catch((error) => {
|
||||
clearTimeout(timeout)
|
||||
socket.removeEventListener('message', onMessage)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
||||
const waitForSelector = async (send, selector, message) => {
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
const result = await send('Runtime.evaluate', {
|
||||
expression: `Boolean(document.querySelector(${JSON.stringify(selector)}))`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (result.result?.value) return
|
||||
if (attempt === 29) throw new Error(message)
|
||||
await sleep(100)
|
||||
}
|
||||
}
|
||||
|
||||
const waitForText = async (send, selector, text, message) => {
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
const result = await send('Runtime.evaluate', {
|
||||
expression: `document.querySelector(${JSON.stringify(selector)})?.textContent.includes(${JSON.stringify(text)}) === true`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (result.result?.value) return
|
||||
if (attempt === 29) throw new Error(message)
|
||||
await sleep(50)
|
||||
}
|
||||
}
|
||||
|
||||
const prepareG03State = async (send, action) => {
|
||||
const actions = new Set([
|
||||
'g03-create-validation',
|
||||
'g03-duplicate-reminder',
|
||||
'g03-create-submitting',
|
||||
'g03-create-error',
|
||||
'g03-ancestor-validation',
|
||||
'g03-ancestor-submitting',
|
||||
'g03-ancestor-error',
|
||||
'g03-ancestor-success'
|
||||
])
|
||||
if (!actions.has(action)) return
|
||||
|
||||
if (action === 'g03-create-validation') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.flow-primary-action').click()" })
|
||||
await waitForSelector(send, '.field-error', 'G03 create validation did not render')
|
||||
return
|
||||
}
|
||||
|
||||
if (action.startsWith('g03-create') || action === 'g03-duplicate-reminder') {
|
||||
const name = action === 'g03-create-error' ? '失败' : '汤氏家谱'
|
||||
const prepared = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const inputs = document.querySelectorAll('.field-row input')
|
||||
if (inputs.length < 4) return false
|
||||
const values = ['汤', ${JSON.stringify(name)}, '', '河南·洛阳']
|
||||
inputs.forEach((input, index) => {
|
||||
input.value = values[index]
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
})
|
||||
document.querySelector('.flow-primary-action').click()
|
||||
return true
|
||||
})()`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (!prepared.result?.value) throw new Error(`Could not prepare ${action}`)
|
||||
await waitForSelector(send, '.duplicate-reminder', `G03 duplicate reminder did not render for ${action}`)
|
||||
if (action === 'g03-duplicate-reminder') return
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.duplicate-reminder__confirm').click()" })
|
||||
if (action === 'g03-create-submitting') {
|
||||
await waitForText(send, '.flow-primary-action__copy', '正在创建', 'G03 create submitting state did not render')
|
||||
return
|
||||
}
|
||||
await waitForSelector(send, '.flow-error', 'G03 create error state did not render')
|
||||
return
|
||||
}
|
||||
|
||||
if (action === 'g03-ancestor-validation') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.flow-primary-action').click()" })
|
||||
await waitForSelector(send, '.field-error', 'G03 ancestor validation did not render')
|
||||
return
|
||||
}
|
||||
|
||||
const personName = action === 'g03-ancestor-error' ? '失败' : '汤远'
|
||||
const prepared = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const input = document.querySelector('.field-row input')
|
||||
if (!input) return false
|
||||
input.value = ${JSON.stringify(personName)}
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
document.querySelector('.flow-primary-action').click()
|
||||
return true
|
||||
})()`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (!prepared.result?.value) throw new Error(`Could not prepare ${action}`)
|
||||
if (action === 'g03-ancestor-submitting') {
|
||||
await waitForText(send, '.flow-primary-action__copy', '正在保存', 'G03 ancestor submitting state did not render')
|
||||
} else if (action === 'g03-ancestor-error') {
|
||||
await waitForSelector(send, '.flow-error', 'G03 ancestor error state did not render')
|
||||
} else {
|
||||
await waitForSelector(send, '.flow-success-layer', 'G03 ancestor success state did not render')
|
||||
}
|
||||
}
|
||||
|
||||
const prepareG08State = async (send, action) => {
|
||||
if (!['g08-validation', 'g08-submitting', 'g08-error', 'g08-success'].includes(action)) return
|
||||
if (action === 'g08-validation') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.join-action').click()" })
|
||||
await waitForSelector(send, '.join-field-error', 'G08 validation errors did not render')
|
||||
return
|
||||
}
|
||||
|
||||
const realName = action === 'g08-error' ? '失败' : '汤明'
|
||||
const prepared = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const inputs = document.querySelectorAll('.join-field input')
|
||||
if (inputs.length < 2) return false
|
||||
const values = [${JSON.stringify(realName)}, '堂侄']
|
||||
inputs.forEach((input, index) => {
|
||||
input.value = values[index]
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
})
|
||||
return true
|
||||
})()`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (!prepared.result?.value) throw new Error(`Could not prepare ${action}`)
|
||||
await sleep(50)
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.join-action').click()" })
|
||||
if (action === 'g08-submitting') {
|
||||
await waitForText(send, '.join-action', '正在', 'G08 submitting state did not render')
|
||||
} else if (action === 'g08-error') {
|
||||
await waitForSelector(send, '.join-state--error', 'G08 error state did not render')
|
||||
} else {
|
||||
await waitForSelector(send, '.join-state--success', 'G08 success state did not render')
|
||||
}
|
||||
}
|
||||
|
||||
const prepareG09State = async (send, action) => {
|
||||
if (!['g09-withdraw-dialog', 'g09-withdrawn'].includes(action)) return
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.application-card__action')?.click()" })
|
||||
await waitForSelector(send, '.app-dialog-layer', 'G09 withdrawal dialog did not render')
|
||||
if (action === 'g09-withdrawn') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.app-dialog__actions .app-button:last-child')?.click()" })
|
||||
await waitForText(send, '.application-card__status', '已撤回', 'G09 withdrawn state did not render')
|
||||
}
|
||||
}
|
||||
|
||||
const prepareG10State = async (send, action) => {
|
||||
const actions = ['g10-help', 'g10-approve-dialog', 'g10-approved', 'g10-reject-dialog', 'g10-rejected']
|
||||
if (!actions.includes(action)) return
|
||||
if (action === 'g10-help') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.header-action')?.click()" })
|
||||
await waitForText(send, '.app-dialog__title', '审核说明', 'G10 help dialog did not render')
|
||||
return
|
||||
}
|
||||
const isApprove = action === 'g10-approve-dialog' || action === 'g10-approved'
|
||||
const selector = isApprove ? '.review-action:last-child' : '.review-action:first-child'
|
||||
await send('Runtime.evaluate', { expression: `document.querySelector(${JSON.stringify(selector)})?.click()` })
|
||||
await waitForSelector(send, '.app-dialog-layer', 'G10 audit confirmation did not render')
|
||||
if (action === 'g10-approved' || action === 'g10-rejected') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.app-dialog__actions .app-button:last-child')?.click()" })
|
||||
await waitForText(send, '.application-card__status', isApprove ? '已通过' : '已拒绝', 'G10 audit result did not render')
|
||||
}
|
||||
}
|
||||
|
||||
const prepareG11State = async (send, action) => {
|
||||
if (!['g11-validation', 'g11-saved'].includes(action)) return
|
||||
if (action === 'g11-validation') {
|
||||
await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const input = document.querySelector('.settings-field input')
|
||||
if (!input) return false
|
||||
input.value = ''
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
document.querySelector('.settings-action')?.click()
|
||||
return true
|
||||
})()`,
|
||||
returnByValue: true
|
||||
})
|
||||
await waitForSelector(send, '.settings-field-error', 'G11 validation state did not render')
|
||||
return
|
||||
}
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.settings-action')?.click()" })
|
||||
await waitForSelector(send, '.settings-state--success', 'G11 saved state did not render')
|
||||
await waitForSelector(send, '.settings-feedback', 'G11 saved feedback did not render')
|
||||
}
|
||||
|
||||
const prepareG12State = async (send, action) => {
|
||||
if (!['g12-validation', 'g12-save-error', 'g12-saved'].includes(action)) return
|
||||
const value = action === 'g12-validation' ? '' : action === 'g12-save-error' ? '失败' : '启宗敦本继世传芳'
|
||||
await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const textarea = document.querySelector('.poem-field textarea')
|
||||
if (!textarea) return false
|
||||
textarea.value = ${JSON.stringify(value)}
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
document.querySelector('.poem-editor__actions .poem-action:last-child')?.click()
|
||||
return true
|
||||
})()`,
|
||||
returnByValue: true
|
||||
})
|
||||
if (action === 'g12-saved') {
|
||||
await waitForSelector(send, '.poem-state--list', 'G12 saved list did not render')
|
||||
await waitForSelector(send, '.poem-feedback', 'G12 saved feedback did not render')
|
||||
} else {
|
||||
await waitForSelector(send, '.poem-field-error', 'G12 validation or save error did not render')
|
||||
}
|
||||
}
|
||||
|
||||
const prepareT03T06State = async (send, action) => {
|
||||
if (action !== 't06-conflict-help') return
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.conflict-actions .member-form-action:last-child')?.click()" })
|
||||
await waitForSelector(send, '.app-dialog-layer', 'T06 conflict help dialog did not render')
|
||||
}
|
||||
|
||||
const prepareFSeriesState = async (send, action) => {
|
||||
if (action === 'f02-validation') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.publish-form .app-button')?.click()" })
|
||||
await waitForSelector(send, '.app-toast', 'F02 empty-content toast did not render')
|
||||
return
|
||||
}
|
||||
if (action !== 'module-preview-toast') return
|
||||
await send('Runtime.evaluate', { expression: "(document.querySelector('.list-card') || document.querySelector('.settings-row'))?.click()" })
|
||||
await waitForSelector(send, '.app-toast', 'ModulePage preview toast did not render')
|
||||
}
|
||||
|
||||
const prepareNSeriesState = async (send, action) => {
|
||||
if (action === 'n01-read-first') {
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.notice-card')?.click()" })
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
const result = await send('Runtime.evaluate', {
|
||||
expression: "document.querySelector('.notice-card')?.textContent.includes('未读提醒') === false",
|
||||
returnByValue: true
|
||||
})
|
||||
if (result.result?.value) return
|
||||
if (attempt === 29) throw new Error('N01 first notice did not enter the read state')
|
||||
await sleep(50)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (action !== 'n01-mark-all') return
|
||||
await send('Runtime.evaluate', { expression: "document.querySelector('.header-action')?.click()" })
|
||||
await waitForSelector(send, '.app-toast', 'N01 mark-all-read toast did not render')
|
||||
}
|
||||
|
||||
const capture = async () => {
|
||||
const { socket, send } = await connect()
|
||||
const viewportWidth = Number(width)
|
||||
@@ -231,7 +492,7 @@ const capture = async () => {
|
||||
await send('Page.navigate', { url })
|
||||
await waitForRequestedUrl(send, url)
|
||||
if (action !== 'fresh-navigation') {
|
||||
await send('Page.reload')
|
||||
await waitForPageLoad(socket, () => send('Page.reload'))
|
||||
await waitForFreshDocument(send, documentTimeOrigin)
|
||||
}
|
||||
|
||||
@@ -247,9 +508,18 @@ const capture = async () => {
|
||||
|
||||
await prepareA01LoginState(send, action)
|
||||
await prepareG06State(send, action)
|
||||
await prepareG03State(send, action)
|
||||
await prepareG08State(send, action)
|
||||
await prepareG09State(send, action)
|
||||
await prepareG10State(send, action)
|
||||
await prepareG11State(send, action)
|
||||
await prepareG12State(send, action)
|
||||
await prepareT03T06State(send, action)
|
||||
await prepareFSeriesState(send, action)
|
||||
await prepareNSeriesState(send, action)
|
||||
|
||||
await waitForLoadedImages(send)
|
||||
await sleep(renderSettleMilliseconds)
|
||||
await sleep(action?.endsWith('-submitting') ? 80 : renderSettleMilliseconds)
|
||||
const screenshot = await send('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false })
|
||||
fs.mkdirSync(path.dirname(output), { recursive: true })
|
||||
fs.writeFileSync(output, Buffer.from(screenshot.data, 'base64'))
|
||||
|
||||
Reference in New Issue
Block a user