mirror of
https://github.com/screenci/screenci.git
synced 2026-09-19 08:57:46 +08:00
fix(cli): upload screenshot recordings as image/png
Screenshot recordings write screenshot.png with output: 'screenshot' instead of recording.mp4, so the upload path failed every still with "Missing recording.mp4". Derive the upload source and content type from data.output: screenshots stream their PNG capture with image/png, videos keep recording.mp4 / video/mp4.
This commit is contained in:
@@ -1438,6 +1438,96 @@ describe('CLI', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('uploads a screenshot recording as image/png from screenshot.png', async () => {
|
||||
mockReaddir.mockResolvedValue(['home'])
|
||||
mockReadFile.mockImplementation(async (path: string | URL) => {
|
||||
const pathString = String(path)
|
||||
if (pathString.endsWith('package.json')) {
|
||||
return JSON.stringify({ version: '0.0.32' })
|
||||
}
|
||||
if (pathString.endsWith('data.json')) {
|
||||
return JSON.stringify({
|
||||
events: [],
|
||||
output: 'screenshot',
|
||||
screenshot: {
|
||||
path: 'screenshot.png',
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
deviceScaleFactor: 1,
|
||||
},
|
||||
metadata: { videoName: 'home' },
|
||||
})
|
||||
}
|
||||
return ''
|
||||
})
|
||||
// The screenshot capture exists, but there is no recording.mp4.
|
||||
mockExistsSync.mockImplementation(
|
||||
(path: string) =>
|
||||
path.endsWith('data.json') || path.endsWith('screenshot.png')
|
||||
)
|
||||
|
||||
let recordingPut: RequestInit | undefined
|
||||
mockFetch.mockImplementation(
|
||||
async (input: string | URL, init?: RequestInit) => {
|
||||
const url = String(input)
|
||||
if (url.endsWith('/cli/upload/start')) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: vi.fn().mockResolvedValue({
|
||||
recordingId: 'recording_123',
|
||||
projectId: 'project_123',
|
||||
}),
|
||||
text: vi.fn().mockResolvedValue(''),
|
||||
}
|
||||
}
|
||||
if (url.endsWith('/cli/upload/recording_123/recording')) {
|
||||
recordingPut = init
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
text: vi.fn().mockResolvedValue(''),
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
text: vi.fn().mockResolvedValue(''),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const { uploadRecordings } = await import('./cli')
|
||||
|
||||
const result = await uploadRecordings(
|
||||
'/repo/.screenci',
|
||||
'Test Project',
|
||||
'https://api.screenci.test',
|
||||
'test-secret'
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
projectId: 'project_123',
|
||||
recordId: expect.any(String),
|
||||
hadFailures: false,
|
||||
studioNotices: [],
|
||||
failedVideoNames: [],
|
||||
failedVideoMessages: [],
|
||||
plan: null,
|
||||
})
|
||||
// The capture is streamed from screenshot.png with an image content type.
|
||||
expect(mockCreateReadStream).toHaveBeenCalledWith(
|
||||
expect.stringContaining('screenshot.png')
|
||||
)
|
||||
expect(
|
||||
(recordingPut?.headers as Record<string, string> | undefined)?.[
|
||||
'Content-Type'
|
||||
]
|
||||
).toBe('image/png')
|
||||
})
|
||||
|
||||
it('fails the upload when an asset check fails', async () => {
|
||||
mockReaddir.mockResolvedValue(['demo-video'])
|
||||
mockReadFile.mockImplementation(async (path: string | URL) => {
|
||||
|
||||
@@ -567,7 +567,15 @@ async function uploadRecordingCandidate(
|
||||
|
||||
try {
|
||||
uploadAbort.throwIfAborted()
|
||||
const recordingPath = resolve(screenciDir, entry, 'recording.mp4')
|
||||
// A screenshot recording uploads its raw page capture (always a PNG) through
|
||||
// the same recording endpoint a video uses; the renderer reads those bytes as
|
||||
// the capture. Videos upload recording.mp4. Output kind defaults to 'video'.
|
||||
const isScreenshot = data.output === 'screenshot'
|
||||
const recordingFileName = isScreenshot
|
||||
? (data.screenshot?.path ?? 'screenshot.png')
|
||||
: 'recording.mp4'
|
||||
const recordingContentType = isScreenshot ? 'image/png' : 'video/mp4'
|
||||
const recordingPath = resolve(screenciDir, entry, recordingFileName)
|
||||
if (!existsSync(recordingPath)) {
|
||||
progressReporter.complete(progressIndex, 'failure')
|
||||
return {
|
||||
@@ -575,7 +583,7 @@ async function uploadRecordingCandidate(
|
||||
videoId: null,
|
||||
hadFailure: true,
|
||||
videoName,
|
||||
failureMessage: `Missing recording.mp4 for "${videoName}"`,
|
||||
failureMessage: `Missing ${recordingFileName} for "${videoName}"`,
|
||||
recordId,
|
||||
}
|
||||
}
|
||||
@@ -665,7 +673,7 @@ async function uploadRecordingCandidate(
|
||||
const fileStat = await stat(recordingPath)
|
||||
if (verbose) {
|
||||
logger.info(
|
||||
`Uploading recording.mp4 size=${(fileStat.size / 1024 / 1024).toFixed(1)}MB`
|
||||
`Uploading ${recordingFileName} size=${(fileStat.size / 1024 / 1024).toFixed(1)}MB`
|
||||
)
|
||||
}
|
||||
const recordingResponse = await withUploadRetry(async () => {
|
||||
@@ -680,7 +688,7 @@ async function uploadRecordingCandidate(
|
||||
return await fetch(`${apiUrl}/cli/upload/${recordingId}/recording`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'video/mp4',
|
||||
'Content-Type': recordingContentType,
|
||||
'Content-Length': String(fileStat.size),
|
||||
'X-ScreenCI-Secret': secret,
|
||||
...(elevenLabsApiKey
|
||||
|
||||
Reference in New Issue
Block a user