feat: add browser artifact transformer pipeline
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
export { parseCaptureBrowserCliArguments } from './cli/capture'
|
||||
export { applyArtifactTransformers, createImageArtifact } from './runtime/artifacts'
|
||||
export { captureBrowserRoots } from './runtime/capture'
|
||||
export { assertUniqueCaptureFilePaths, captureFilePath, sanitizeOutputName } from './runtime/files'
|
||||
export { artifactFilePath, assertUniqueCaptureFilePaths, captureFilePath, sanitizeOutputName } from './runtime/files'
|
||||
export { captureRootSelector } from './runtime/selectors'
|
||||
export type { CaptureBrowserCliArguments } from './runtime/types'
|
||||
export type { BrowserCaptureRequest, CapturedRootArtifact } from './runtime/types'
|
||||
export type {
|
||||
ArtifactTransformer,
|
||||
BrowserCaptureRequest,
|
||||
VishotArtifact,
|
||||
VishotArtifactKind,
|
||||
VishotArtifactStage,
|
||||
} from './runtime/types'
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
applyArtifactTransformers,
|
||||
createImageArtifact,
|
||||
} from './artifacts'
|
||||
|
||||
describe('applyArtifactTransformers', () => {
|
||||
it('returns the original artifact when no transformers are configured', async () => {
|
||||
const artifact = createImageArtifact({
|
||||
artifactName: 'intro-chat-window',
|
||||
filePath: '/tmp/intro-chat-window.png',
|
||||
stage: 'browser-final',
|
||||
})
|
||||
|
||||
expect(artifact).not.toHaveProperty('rootName')
|
||||
await expect(applyArtifactTransformers(artifact, [])).resolves.toEqual([artifact])
|
||||
})
|
||||
|
||||
it('passes the generated artifact through each transformer in order', async () => {
|
||||
const first = vi.fn(async artifact => ({
|
||||
...artifact,
|
||||
filePath: '/tmp/intro-chat-window.avif',
|
||||
format: 'avif',
|
||||
}))
|
||||
const second = vi.fn(async artifact => ({
|
||||
...artifact,
|
||||
metadata: { optimized: true },
|
||||
}))
|
||||
|
||||
const artifact = createImageArtifact({
|
||||
artifactName: 'intro-chat-window',
|
||||
filePath: '/tmp/intro-chat-window.png',
|
||||
stage: 'browser-final',
|
||||
})
|
||||
|
||||
expect(artifact).not.toHaveProperty('rootName')
|
||||
const result = await applyArtifactTransformers(artifact, [first, second])
|
||||
|
||||
expect(first).toHaveBeenCalledTimes(1)
|
||||
expect(second).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
filePath: '/tmp/intro-chat-window.avif',
|
||||
format: 'avif',
|
||||
metadata: { optimized: true },
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { ArtifactTransformer, VishotArtifact, VishotArtifactStage } from './types'
|
||||
|
||||
export function createImageArtifact(options: {
|
||||
artifactName: string
|
||||
filePath: string
|
||||
stage: VishotArtifactStage
|
||||
metadata?: Record<string, unknown>
|
||||
}): VishotArtifact {
|
||||
return {
|
||||
artifactName: options.artifactName,
|
||||
filePath: options.filePath,
|
||||
format: 'png',
|
||||
kind: 'image',
|
||||
metadata: options.metadata,
|
||||
stage: options.stage,
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyArtifactTransformers(
|
||||
artifact: VishotArtifact,
|
||||
transformers: ArtifactTransformer[] | undefined,
|
||||
): Promise<VishotArtifact[]> {
|
||||
let currentArtifacts: VishotArtifact[] = [artifact]
|
||||
|
||||
for (const transformer of transformers ?? []) {
|
||||
const nextArtifacts: VishotArtifact[] = []
|
||||
|
||||
for (const currentArtifact of currentArtifacts) {
|
||||
const transformed = await transformer(currentArtifact)
|
||||
nextArtifacts.push(...(Array.isArray(transformed) ? transformed : [transformed]))
|
||||
}
|
||||
|
||||
currentArtifacts = nextArtifacts
|
||||
}
|
||||
|
||||
return currentArtifacts
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Page } from 'playwright'
|
||||
|
||||
import type { BrowserCaptureRequest, CapturedRootArtifact } from './types'
|
||||
import type { BrowserCaptureRequest, VishotArtifact } from './types'
|
||||
|
||||
import path from 'node:path'
|
||||
|
||||
@@ -8,6 +8,7 @@ import { mkdir } from 'node:fs/promises'
|
||||
|
||||
import { chromium } from 'playwright'
|
||||
|
||||
import { applyArtifactTransformers, createImageArtifact } from './artifacts'
|
||||
import { assertUniqueCaptureFilePaths, captureFilePath } from './files'
|
||||
import { captureRootSelector } from './selectors'
|
||||
import { startSceneViteServer } from './vite-server'
|
||||
@@ -34,7 +35,7 @@ async function waitForScenarioReady(page: Page): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
async function captureRoot(page: Page, outputDir: string, rootName: string): Promise<CapturedRootArtifact> {
|
||||
async function captureRoot(page: Page, outputDir: string, rootName: string): Promise<VishotArtifact> {
|
||||
const filePath = captureFilePath(outputDir, rootName)
|
||||
const locator = page.locator(captureRootSelector(rootName))
|
||||
|
||||
@@ -44,10 +45,11 @@ async function captureRoot(page: Page, outputDir: string, rootName: string): Pro
|
||||
path: filePath,
|
||||
})
|
||||
|
||||
return {
|
||||
rootName,
|
||||
return createImageArtifact({
|
||||
artifactName: rootName,
|
||||
filePath,
|
||||
}
|
||||
stage: 'browser-final',
|
||||
})
|
||||
}
|
||||
|
||||
async function resolveBaseUrl(request: BrowserCaptureRequest): Promise<{ baseUrl: string, closeServer?: () => Promise<void> }> {
|
||||
@@ -67,7 +69,7 @@ async function resolveBaseUrl(request: BrowserCaptureRequest): Promise<{ baseUrl
|
||||
throw new Error('Browser capture requires either "baseUrl" or "sceneAppRoot"')
|
||||
}
|
||||
|
||||
export async function captureBrowserRoots(request: BrowserCaptureRequest): Promise<CapturedRootArtifact[]> {
|
||||
export async function captureBrowserRoots(request: BrowserCaptureRequest): Promise<VishotArtifact[]> {
|
||||
const { baseUrl, closeServer } = await resolveBaseUrl(request)
|
||||
let browser: Awaited<ReturnType<typeof chromium.launch>> | undefined
|
||||
|
||||
@@ -95,10 +97,11 @@ export async function captureBrowserRoots(request: BrowserCaptureRequest): Promi
|
||||
|
||||
assertUniqueCaptureFilePaths(rootNames)
|
||||
|
||||
const artifacts: CapturedRootArtifact[] = []
|
||||
const artifacts: VishotArtifact[] = []
|
||||
|
||||
for (const rootName of rootNames) {
|
||||
artifacts.push(await captureRoot(page, request.outputDir, rootName))
|
||||
const artifact = await captureRoot(page, request.outputDir, rootName)
|
||||
artifacts.push(...await applyArtifactTransformers(artifact, request.imageTransformers))
|
||||
}
|
||||
|
||||
return artifacts
|
||||
|
||||
@@ -13,8 +13,12 @@ export function sanitizeOutputName(name: string): string {
|
||||
return sanitized.length > 0 ? sanitized : 'capture'
|
||||
}
|
||||
|
||||
export function artifactFilePath(outputDir: string, artifactName: string, format: string): string {
|
||||
return path.resolve(outputDir, `${sanitizeOutputName(artifactName)}.${format}`)
|
||||
}
|
||||
|
||||
export function captureFilePath(outputDir: string, rootName: string): string {
|
||||
return path.resolve(outputDir, `${sanitizeOutputName(rootName)}.png`)
|
||||
return artifactFilePath(outputDir, rootName, 'png')
|
||||
}
|
||||
|
||||
export function assertUniqueCaptureFilePaths(rootNames: string[]): void {
|
||||
|
||||
@@ -193,7 +193,7 @@ describe('captureBrowserRoots', () => {
|
||||
outputDir,
|
||||
})
|
||||
|
||||
expect(artifacts.map(item => item.rootName)).toEqual([
|
||||
expect(artifacts.map(item => item.artifactName)).toEqual([
|
||||
'intro-chat-window',
|
||||
'intro-websocket-settings',
|
||||
])
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface BrowserCaptureRequest {
|
||||
routePath: string
|
||||
outputDir: string
|
||||
rootNames?: string[]
|
||||
imageTransformers?: ArtifactTransformer[]
|
||||
viewport?: {
|
||||
width: number
|
||||
height: number
|
||||
@@ -17,7 +18,18 @@ export interface BrowserCaptureRequest {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CapturedRootArtifact {
|
||||
rootName: string
|
||||
export type VishotArtifactKind = 'image' | 'video'
|
||||
export type VishotArtifactStage = 'browser-final' | 'electron-raw'
|
||||
|
||||
export interface VishotArtifact {
|
||||
kind: VishotArtifactKind
|
||||
stage: VishotArtifactStage
|
||||
artifactName: string
|
||||
filePath: string
|
||||
format: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type ArtifactTransformer = (
|
||||
artifact: VishotArtifact,
|
||||
) => Promise<VishotArtifact | VishotArtifact[]>
|
||||
|
||||
Reference in New Issue
Block a user