@@ -3,7 +3,7 @@ import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import type { Page } from 'playwright-core'
import { clickText , injectSession , launchBrowser , login , newContext , waitPage } from './browser.js'
import { BASE_URL , ELEM_DIR , SAMPLE_BILL } from './config.js'
import { BASE_URL , ELEM_DIR , SAMPLE_BILL , SAMPLE_IMAGE , SAMPLE_IMAGE2 } from './config.js'
/** 页面就绪标志:列表页表格、设置面板或卡片 */
const PAGE_READY = '.n-data-table, .config-card, .n-card'
@@ -26,6 +26,7 @@ const MODAL_SCENES: Array<[string, string, string]> = [
[ 'add-rule' , '/rules' , '新增规则' ] ,
[ 'add-email-config' , '/email-bills/configs' , '新增' ] ,
[ 'add-channel' , '/settings' , '添加渠道' ] ,
[ 'channel-create' , '/channels' , '新建渠道' ] ,
]
/**
@@ -110,6 +111,101 @@ export async function captureBillParse(page: Page): Promise<void> {
}
}
/**
* 截取「图片记账」:多图上传 Tab 与识别结果弹窗。
*
* @param page Page 实例
* @param importRecords 识别后是否导入勾选记录(默认 true,便于后续截取来源图片)
*/
export async function captureAiRecordImage ( page : Page , importRecords = true ) : Promise < void > {
try {
await page . setViewportSize ( { width : 1440 , height : 1000 } )
await page . goto ( BASE_URL + '/transactions' )
await waitPage ( page , { selector : PAGE_READY } )
if ( ! ( await clickText ( page , '智能记账' ) ) ) throw new Error ( '未找到「智能记账」按钮' )
const modal = page . locator ( '.n-modal' ) . first ( )
await modal . waitFor ( { state : 'visible' , timeout : 20000 } )
// 切到「图片记账」标签页,上传两张虚构账单截图后截取上传界面
await modal . locator ( '.n-tabs-tab' , { hasText : '图片记账' } ) . first ( ) . click ( )
await page . waitForTimeout ( 600 )
await modal . locator ( "input[type='file']" ) . setInputFiles ( [ SAMPLE_IMAGE , SAMPLE_IMAGE2 ] )
await page . waitForTimeout ( 600 )
await modal . screenshot ( { path : resolve ( ELEM_DIR , 'ai-record-image.png' ) } )
console . log ( '[OK] modal/ai-record-image' )
const recognize = modal . locator ( 'button' , { hasText : '识别图片' } ) . first ( )
await recognize . evaluate ( ( el ) = > ( el as HTMLElement ) . click ( ) )
// 结果弹窗出现(标题「图片记账结果」)且表格有数据
const resultModal = page . locator ( '.n-modal' , { hasText : '图片记账结果' } ) . first ( )
await resultModal . waitFor ( { state : 'visible' , timeout : 180000 } )
await resultModal . locator ( '.n-data-table-tbody tr' ) . first ( ) . waitFor ( { state : 'visible' , timeout : 180000 } )
await page . waitForLoadState ( 'networkidle' , { timeout : 20000 } ) . catch ( ( ) = > undefined )
await page . waitForTimeout ( 800 )
await resultModal . screenshot ( { path : resolve ( ELEM_DIR , 'ai-record-result.png' ) } )
console . log ( '[OK] modal/ai-record-result' )
// 导入勾选记录,便于后续「来源图片」截图(去重键保证重复执行不会产生新记录)
if ( importRecords ) {
const importBtn = resultModal . locator ( 'button' , { hasText : '导入勾选' } ) . first ( )
await importBtn . evaluate ( ( el ) = > ( el as HTMLElement ) . click ( ) )
await resultModal . waitFor ( { state : 'hidden' , timeout : 60000 } ) . catch ( ( ) = > undefined )
await page . waitForTimeout ( 1000 )
console . log ( '[OK] modal/ai-record-import' )
}
} catch ( err ) {
console . log ( ` [FAIL] modal/ai-record-image: ${ err } ` )
}
}
/**
* 截取「来源图片」查看弹窗(点击表格中图片记账记录的「查看(N)」)。
*
* @param page Page 实例
*/
export async function captureSourceImages ( page : Page ) : Promise < void > {
try {
await page . setViewportSize ( { width : 1440 , height : 1000 } )
await page . goto ( BASE_URL + '/transactions' )
await waitPage ( page , { selector : '.n-data-table-tbody tr' } )
const viewBtn = page . locator ( 'button' , { hasText : /查看(\d+) / } ) . first ( )
await viewBtn . waitFor ( { state : 'visible' , timeout : 20000 } )
await viewBtn . evaluate ( ( el ) = > ( el as HTMLElement ) . click ( ) )
const modal = page . locator ( '.n-modal' , { hasText : '来源图片' } ) . first ( )
await modal . waitFor ( { state : 'visible' , timeout : 20000 } )
await page . waitForLoadState ( 'networkidle' , { timeout : 20000 } ) . catch ( ( ) = > undefined )
await page . waitForTimeout ( 1000 )
await modal . screenshot ( { path : resolve ( ELEM_DIR , 'source-images.png' ) } )
console . log ( '[OK] modal/source-images' )
} catch ( err ) {
console . log ( ` [FAIL] modal/source-images: ${ err } ` )
}
}
/**
* 截取编辑记录弹窗中「交易渠道」的 AI 推荐与新建按钮。
*
* @param page Page 实例
*/
export async function captureChannelAi ( page : Page ) : Promise < void > {
try {
await page . setViewportSize ( { width : 1440 , height : 1000 } )
await page . goto ( BASE_URL + '/transactions' )
await waitPage ( page , { selector : PAGE_READY } )
const editBtn = page . locator ( 'tbody tr' ) . first ( ) . locator ( 'button' , { hasText : '编辑' } ) . first ( )
await editBtn . waitFor ( { state : 'visible' , timeout : 20000 } )
await editBtn . click ( )
const modal = page . locator ( '.n-modal' ) . first ( )
await modal . waitFor ( { state : 'visible' , timeout : 20000 } )
await page . waitForTimeout ( 800 )
await modal . screenshot ( { path : resolve ( ELEM_DIR , 'ai-channel-suggest.png' ) } )
console . log ( '[OK] modal/ai-channel-suggest' )
} catch ( err ) {
console . log ( ` [FAIL] modal/ai-channel-suggest: ${ err } ` )
}
}
/**
* 截取「点击趋势柱状图跳转收支记录并自动带入筛选条件」的筛选卡片。
*
@@ -228,15 +324,17 @@ export async function captureAiRule(page: Page): Promise<void> {
}
/**
* 截取系统设置的六个 配置面板。
* 截取系统设置的配置面板。
*
* @param page Page 实例
* @param only 仅截取指定标题的面板(可选,缺省截取全部)
*/
export async function captureSettingsPanels ( page : Page ) : Promise < void > {
export async function captureSettingsPanels ( page : Page , only? : string [ ] ) : Promise < void > {
await page . setViewportSize ( { width : 1440 , height : 1200 } )
await page . goto ( BASE_URL + '/settings' )
await waitPage ( page , { selector : PAGE_READY } )
for ( const [ name , title ] of SETTINGS_PANELS ) {
const panels = only ? SETTINGS_PANELS . filter ( ( [ , title ] ) = > only . includes ( title ) ) : SETTINGS_PANELS
for ( const [ name , title ] of panels ) {
try {
const card = page . locator ( '.config-card' , { hasText : title } ) . first ( )
await card . waitFor ( { state : 'visible' , timeout : 20000 } )
@@ -265,6 +363,9 @@ async function main(): Promise<void> {
await captureModals ( page )
await captureEditRecord ( page )
await captureAiRule ( page )
await captureAiRecordImage ( page )
await captureSourceImages ( page )
await captureChannelAi ( page )
await captureSettingsPanels ( page )
await captureBillParse ( page )
await captureTrendJump ( page )