The vi.mock('fs') factory returned { default: { ... } } causing fs.mkdirSync
to be undefined at runtime. Add top-level exports alongside default for ESM/CJS interop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
/**
|
|
* Tests for ERP Error Context Capture with screenshot support
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
// Mock logger/shared before importing the module under test
|
|
vi.mock('../../../../src/main/services/logger/shared', () => ({
|
|
getLogDir: vi.fn(() => '/tmp/test-logs')
|
|
}))
|
|
|
|
// Mock fs
|
|
vi.mock('fs', () => ({
|
|
default: {
|
|
mkdirSync: vi.fn(),
|
|
writeFileSync: vi.fn(),
|
|
existsSync: vi.fn(() => true),
|
|
readdirSync: vi.fn(() => []),
|
|
statSync: vi.fn()
|
|
},
|
|
mkdirSync: vi.fn(),
|
|
writeFileSync: vi.fn(),
|
|
existsSync: vi.fn(() => true),
|
|
readdirSync: vi.fn(() => []),
|
|
statSync: vi.fn()
|
|
}))
|
|
|
|
import { capturePageContext } from '../../../../src/main/services/erp/erp-error-context'
|
|
|
|
function createMockPage(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
url: vi.fn(() => 'https://erp.example.com/page'),
|
|
frames: vi.fn(() => [
|
|
{ name: () => 'main', url: () => 'https://erp.example.com/main' },
|
|
{ name: () => 'forwardFrame', url: () => 'https://erp.example.com/frame' }
|
|
]),
|
|
isClosed: vi.fn(() => false),
|
|
screenshot: vi.fn(() => Buffer.from('fake-png-data')),
|
|
...overrides
|
|
} as any
|
|
}
|
|
|
|
describe('capturePageContext', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.mocked(fs.mkdirSync).mockReturnValue(undefined)
|
|
vi.mocked(fs.writeFileSync).mockReturnValue(undefined)
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('should capture screenshot and return screenshotPath on success', async () => {
|
|
const page = createMockPage()
|
|
|
|
const ctx = await capturePageContext(page, '#selector', 'login.username')
|
|
|
|
expect(ctx.screenshotPath).toBeDefined()
|
|
expect(ctx.screenshotPath).toContain('err_')
|
|
expect(ctx.screenshotPath).toContain('.png')
|
|
expect(ctx.screenshotPath).toContain('login_username')
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
expect.stringContaining('.png'),
|
|
expect.any(Buffer)
|
|
)
|
|
})
|
|
|
|
it('should return screenshotPath undefined when page is closed', async () => {
|
|
const page = createMockPage({ isClosed: vi.fn(() => true) })
|
|
|
|
const ctx = await capturePageContext(page)
|
|
|
|
expect(ctx.screenshotPath).toBeUndefined()
|
|
expect(fs.writeFileSync).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should still return other fields when screenshot fails', async () => {
|
|
const page = createMockPage({
|
|
screenshot: vi.fn(() => {
|
|
throw new Error('screenshot timeout')
|
|
})
|
|
})
|
|
|
|
const ctx = await capturePageContext(page, '#target', 'test-step')
|
|
|
|
// Other fields should still be populated
|
|
expect(ctx.pageUrl).toBe('https://erp.example.com/page')
|
|
expect(ctx.frameHierarchy).toHaveLength(2)
|
|
expect(ctx.targetSelector).toBe('#target')
|
|
expect(ctx.step).toBe('test-step')
|
|
// Screenshot should be undefined due to failure
|
|
expect(ctx.screenshotPath).toBeUndefined()
|
|
})
|
|
|
|
it('should sanitize special characters in step name for filename', async () => {
|
|
const page = createMockPage()
|
|
|
|
const ctx = await capturePageContext(page, undefined, 'login/user:test*step')
|
|
|
|
expect(ctx.screenshotPath).toBeDefined()
|
|
expect(ctx.screenshotPath).toContain('login_user_test_step')
|
|
})
|
|
|
|
it('should use "unknown" in filename when step is not provided', async () => {
|
|
const page = createMockPage()
|
|
|
|
const ctx = await capturePageContext(page)
|
|
|
|
expect(ctx.screenshotPath).toBeDefined()
|
|
expect(ctx.screenshotPath).toContain('unknown')
|
|
})
|
|
|
|
it('should truncate long step names to 40 characters', async () => {
|
|
const page = createMockPage()
|
|
const longStep = 'a'.repeat(80)
|
|
|
|
const ctx = await capturePageContext(page, undefined, longStep)
|
|
|
|
expect(ctx.screenshotPath).toBeDefined()
|
|
const filename = path.basename(ctx.screenshotPath!)
|
|
// Extract step portion: err_YYYYMMDD_HHmmss_<step>.png
|
|
const stepPart = filename.replace(/^err_\d{8}_\d{6}_/, '').replace(/\.png$/, '')
|
|
expect(stepPart.length).toBe(40)
|
|
})
|
|
|
|
it('should create screenshots directory if it does not exist', async () => {
|
|
const page = createMockPage()
|
|
|
|
await capturePageContext(page)
|
|
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('screenshots'), {
|
|
recursive: true
|
|
})
|
|
})
|
|
})
|