mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
test: add unit tests for test-recorder transform and improve README
- Add 25 colocated unit tests for transform rules and engine - Add tools/ to vitest include pattern - Improve README with quick-start prereqs and test instructions
This commit is contained in:
@@ -2,15 +2,15 @@
|
||||
|
||||
Interactive CLI for recording and transforming Playwright browser tests for ComfyUI.
|
||||
|
||||
## Usage
|
||||
## Quick Start
|
||||
|
||||
From the repo root:
|
||||
**Prerequisites:** Node.js ≥ 20, pnpm, a running ComfyUI backend. See the [Browser Tests README](../../browser_tests/README.md) for detailed environment setup including Playwright installation and backend configuration.
|
||||
|
||||
```bash
|
||||
pnpm comfy-test record # Record a new test
|
||||
pnpm comfy-test check # Verify your environment is ready
|
||||
pnpm comfy-test record # Record a new test
|
||||
pnpm comfy-test transform <file> # Transform raw codegen to conventions
|
||||
pnpm comfy-test check # Check environment prerequisites
|
||||
pnpm comfy-test list # List available workflows
|
||||
pnpm comfy-test list # List available workflows
|
||||
```
|
||||
|
||||
## For QA Testers
|
||||
@@ -24,3 +24,9 @@ cd tools/test-recorder
|
||||
pnpm build # Compile TypeScript
|
||||
pnpm dev # Watch mode
|
||||
```
|
||||
|
||||
Run unit tests from the repo root:
|
||||
|
||||
```bash
|
||||
pnpm test:unit -- tools/test-recorder
|
||||
```
|
||||
|
||||
109
tools/test-recorder/src/transform/engine.test.ts
Normal file
109
tools/test-recorder/src/transform/engine.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { transform, formatTransformSummary } from './engine'
|
||||
|
||||
describe('transform', () => {
|
||||
const rawCodegenOutput = `import { test, expect } from '@playwright/test'
|
||||
|
||||
test('my test', async ({ page }) => {
|
||||
await page.goto('http://localhost:8188')
|
||||
await page.locator('canvas').click()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.getByPlaceholder('Search Nodes...').fill('KSampler')
|
||||
})`
|
||||
|
||||
it('applies all applicable regex rules', () => {
|
||||
const result = transform(rawCodegenOutput, {
|
||||
testName: 'canvas-test',
|
||||
tags: ['@canvas']
|
||||
})
|
||||
expect(result.code).toContain('comfyPageFixture as test')
|
||||
expect(result.code).toContain('async ({ comfyPage })')
|
||||
expect(result.code).not.toContain('page.goto')
|
||||
expect(result.code).toContain('comfyPage.canvas')
|
||||
expect(result.code).toContain('comfyPage.nextFrame()')
|
||||
expect(result.code).toContain('comfyPage.searchBox.input')
|
||||
})
|
||||
|
||||
it('wraps test in describe block', () => {
|
||||
const result = transform(rawCodegenOutput, {
|
||||
testName: 'canvas-test',
|
||||
tags: ['@canvas']
|
||||
})
|
||||
expect(result.code).toContain('test.describe(')
|
||||
expect(result.code).toContain('"canvas test"')
|
||||
})
|
||||
|
||||
it('tracks applied rules', () => {
|
||||
const result = transform(rawCodegenOutput, { testName: 'test' })
|
||||
const ruleNames = result.appliedRules.map((r) => r.name)
|
||||
expect(ruleNames).toContain('replace-test-import')
|
||||
expect(ruleNames).toContain('replace-page-destructure')
|
||||
expect(ruleNames).toContain('remove-goto')
|
||||
expect(ruleNames).toContain('replace-canvas-locator')
|
||||
expect(ruleNames).toContain('replace-waitForTimeout')
|
||||
expect(ruleNames).toContain('wrap-in-describe')
|
||||
})
|
||||
|
||||
it('warns about remaining pixel coordinates', () => {
|
||||
const input = `import { test } from '@playwright/test'
|
||||
|
||||
test('pos test', async ({ page }) => {
|
||||
await page.click({ position: { x: 100, y: 200 } })
|
||||
})`
|
||||
const result = transform(input)
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.stringContaining('pixel coordinates')
|
||||
)
|
||||
})
|
||||
|
||||
it('uses default testName and tags when not provided', () => {
|
||||
const result = transform(rawCodegenOutput)
|
||||
expect(result.code).toContain('"unnamed test"')
|
||||
expect(result.code).toContain('"@canvas"')
|
||||
})
|
||||
|
||||
it('collapses triple blank lines', () => {
|
||||
const input = `import { test } from '@playwright/test'
|
||||
|
||||
|
||||
|
||||
test('x', async ({ page }) => {})`
|
||||
const result = transform(input)
|
||||
expect(result.code).not.toMatch(/\n{3,}/)
|
||||
})
|
||||
|
||||
it('returns code ending with a single newline', () => {
|
||||
const result = transform(rawCodegenOutput)
|
||||
expect(result.code).toMatch(/[^\n]\n$/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatTransformSummary', () => {
|
||||
it('formats applied rules with checkmarks', () => {
|
||||
const lines = formatTransformSummary({
|
||||
code: '',
|
||||
appliedRules: [{ name: 'test-rule', description: 'Did a thing' }],
|
||||
warnings: []
|
||||
})
|
||||
expect(lines).toEqual(['✅ Did a thing'])
|
||||
})
|
||||
|
||||
it('formats warnings', () => {
|
||||
const lines = formatTransformSummary({
|
||||
code: '',
|
||||
appliedRules: [],
|
||||
warnings: ['Something is wrong']
|
||||
})
|
||||
expect(lines).toEqual(['⚠️ Something is wrong'])
|
||||
})
|
||||
|
||||
it('returns empty array when no rules or warnings', () => {
|
||||
const lines = formatTransformSummary({
|
||||
code: '',
|
||||
appliedRules: [],
|
||||
warnings: []
|
||||
})
|
||||
expect(lines).toEqual([])
|
||||
})
|
||||
})
|
||||
141
tools/test-recorder/src/transform/rules.test.ts
Normal file
141
tools/test-recorder/src/transform/rules.test.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { transformRules, structuralTransforms } from './rules'
|
||||
|
||||
describe('transformRules', () => {
|
||||
function applyRule(ruleName: string, input: string): string {
|
||||
const rule = transformRules.find((r) => r.name === ruleName)
|
||||
if (!rule) throw new Error(`Rule not found: ${ruleName}`)
|
||||
if (typeof rule.replacement === 'string') {
|
||||
return input.replace(rule.pattern, rule.replacement)
|
||||
}
|
||||
return input.replace(
|
||||
rule.pattern,
|
||||
rule.replacement as (...args: string[]) => string
|
||||
)
|
||||
}
|
||||
|
||||
describe('import transforms', () => {
|
||||
it('replaces { test, expect } from @playwright/test', () => {
|
||||
const input = `import { test, expect } from '@playwright/test'`
|
||||
const result = applyRule('replace-test-import', input)
|
||||
expect(result).toContain('comfyPageFixture as test')
|
||||
expect(result).toContain('comfyExpect as expect')
|
||||
expect(result).toContain("from '../fixtures/ComfyPage'")
|
||||
})
|
||||
|
||||
it('replaces { expect, test } (reversed order)', () => {
|
||||
const input = `import { expect, test } from '@playwright/test'`
|
||||
const result = applyRule('replace-test-import', input)
|
||||
expect(result).toContain('comfyPageFixture as test')
|
||||
})
|
||||
|
||||
it('replaces test-only import', () => {
|
||||
const input = `import { test } from '@playwright/test'`
|
||||
const result = applyRule('replace-test-only-import', input)
|
||||
expect(result).toContain('comfyPageFixture as test')
|
||||
expect(result).not.toContain('expect')
|
||||
})
|
||||
|
||||
it('replaces expect-only import', () => {
|
||||
const input = `import { expect } from '@playwright/test'`
|
||||
const result = applyRule('replace-expect-only-import', input)
|
||||
expect(result).toContain('comfyExpect as expect')
|
||||
expect(result).not.toContain('comfyPageFixture')
|
||||
})
|
||||
})
|
||||
|
||||
describe('fixture transforms', () => {
|
||||
it('replaces { page } with { comfyPage }', () => {
|
||||
const input = `test('my test', async ({ page }) => {`
|
||||
const result = applyRule('replace-page-destructure', input)
|
||||
expect(result).toContain('async ({ comfyPage })')
|
||||
expect(result).not.toContain('{ page }')
|
||||
})
|
||||
})
|
||||
|
||||
describe('locator transforms', () => {
|
||||
it('removes page.goto calls', () => {
|
||||
const input = ` await page.goto('http://localhost:8188')\n await page.click('button')`
|
||||
const result = applyRule('remove-goto', input)
|
||||
expect(result).not.toContain('page.goto')
|
||||
expect(result).toContain('page.click')
|
||||
})
|
||||
|
||||
it('replaces page.locator("canvas")', () => {
|
||||
const input = `await page.locator('canvas').click()`
|
||||
const result = applyRule('replace-canvas-locator', input)
|
||||
expect(result).toBe('await comfyPage.canvas.click()')
|
||||
})
|
||||
|
||||
it('replaces search box placeholder', () => {
|
||||
const input = `page.getByPlaceholder('Search Nodes...')`
|
||||
const result = applyRule('replace-search-placeholder', input)
|
||||
expect(result).toBe('comfyPage.searchBox.input')
|
||||
})
|
||||
|
||||
it('replaces bare page. references with comfyPage.page.', () => {
|
||||
const input = `await page.click('button')`
|
||||
const result = applyRule('replace-bare-page', input)
|
||||
expect(result).toBe(`await comfyPage.page.click('button')`)
|
||||
})
|
||||
|
||||
it('does not replace comfyPage.page. (no double-replace)', () => {
|
||||
const input = `await comfyPage.page.click('button')`
|
||||
const result = applyRule('replace-bare-page', input)
|
||||
expect(result).toBe(input)
|
||||
})
|
||||
})
|
||||
|
||||
describe('wait transforms', () => {
|
||||
it('replaces waitForTimeout with nextFrame', () => {
|
||||
const input = `await page.waitForTimeout(1000);`
|
||||
const result = applyRule('replace-waitForTimeout', input)
|
||||
expect(result).toBe('await comfyPage.nextFrame()')
|
||||
})
|
||||
|
||||
it('handles waitForTimeout without semicolon', () => {
|
||||
const input = `await page.waitForTimeout(500)`
|
||||
const result = applyRule('replace-waitForTimeout', input)
|
||||
expect(result).toBe('await comfyPage.nextFrame()')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('structuralTransforms', () => {
|
||||
const wrapInDescribe = structuralTransforms.find(
|
||||
(t) => t.name === 'wrap-in-describe'
|
||||
)!
|
||||
|
||||
it('wraps a test in test.describe with tags', () => {
|
||||
const input = `import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test('does something', async ({ comfyPage }) => {
|
||||
await comfyPage.canvas.click()
|
||||
})`
|
||||
|
||||
const result = wrapInDescribe.apply(input, 'my-test', ['@canvas'])
|
||||
expect(result).toContain('test.describe(')
|
||||
expect(result).toContain('"my test"')
|
||||
expect(result).toContain('"@canvas"')
|
||||
expect(result).toContain('test.afterEach')
|
||||
expect(result).toContain('resetView')
|
||||
})
|
||||
|
||||
it('skips wrapping when test.describe already exists', () => {
|
||||
const input = `test.describe('existing', () => {
|
||||
test('inner', async ({ comfyPage }) => {})
|
||||
})`
|
||||
const result = wrapInDescribe.apply(input, 'test', ['@canvas'])
|
||||
expect(result).toBe(input)
|
||||
})
|
||||
|
||||
it('converts hyphens and underscores to spaces in describe name', () => {
|
||||
const input = `import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test('x', async ({ comfyPage }) => {})`
|
||||
|
||||
const result = wrapInDescribe.apply(input, 'my_test-name', ['@canvas'])
|
||||
expect(result).toContain('"my test name"')
|
||||
})
|
||||
})
|
||||
@@ -650,7 +650,8 @@ export default defineConfig({
|
||||
include: [
|
||||
'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
||||
'packages/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
||||
'scripts/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'
|
||||
'scripts/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
||||
'tools/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'
|
||||
],
|
||||
coverage: {
|
||||
reporter: ['text', 'json', 'html']
|
||||
|
||||
Reference in New Issue
Block a user