Skip to main content
Glama
ExampleValidation.browser.test.ts11.7 kB
/** * Real browser validation tests for example patterns * These tests use actual Playwright browser automation against strudel.cc * Run with: HEADLESS=true npm test -- ExampleValidation.browser * * NOTE: These tests are skipped when running with coverage because Jest's * coverage instrumentation injects variables that don't exist in the browser * context when code runs inside page.evaluate(). */ import { StrudelController } from '../../StrudelController'; import * as fs from 'fs'; import * as path from 'path'; // Skip browser tests when running with coverage to avoid instrumentation conflicts // Jest injects coverage variables like cov_* which don't exist in browser context const isRunningCoverage = process.env.COVERAGE === 'true' || process.env.npm_lifecycle_event?.includes('coverage') || process.argv.some(arg => arg.includes('--coverage')) || typeof (global as any).__coverage__ !== 'undefined'; const describeOrSkip = isRunningCoverage ? describe.skip : describe; describeOrSkip('Browser Validation: Example Patterns', () => { let controller: StrudelController; const examplesDir = path.join(__dirname, '../../../patterns/examples'); const isCI = process.env.CI === 'true'; const headless = process.env.HEADLESS === 'true' || isCI; // Increase timeout for real browser operations jest.setTimeout(30000); beforeAll(async () => { controller = new StrudelController(headless); await controller.initialize(); }); afterAll(async () => { await controller.cleanup(); }); // Helper to load example file function loadExample(genre: string, filename: string) { const filePath = path.join(examplesDir, genre, filename); if (!fs.existsSync(filePath)) { throw new Error(`Example not found: ${filePath}`); } return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } describe('Techno Examples', () => { it('should load and play hard-techno.json', async () => { const example = loadExample('techno', 'hard-techno.json'); await controller.writePattern(example.pattern); const written = await controller.getCurrentPattern(); expect(written).toContain('techno'); expect(written.length).toBeGreaterThan(50); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); const stats = await controller.getPatternStats(); expect(stats.lines).toBeGreaterThan(0); await controller.stop(); }); it('should load and play minimal-techno.json', async () => { const example = loadExample('techno', 'minimal-techno.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); await controller.stop(); }); }); describe('House Examples', () => { it('should load and play deep-house.json', async () => { const example = loadExample('house', 'deep-house.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); await controller.stop(); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); }); it('should load and play tech-house.json', async () => { const example = loadExample('house', 'tech-house.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); await controller.stop(); }); }); describe('Drum & Bass Examples', () => { it('should load and play liquid-dnb.json', async () => { const example = loadExample('dnb', 'liquid-dnb.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); await controller.stop(); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); }); it('should load and play neurofunk.json', async () => { const example = loadExample('dnb', 'neurofunk.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); await controller.stop(); }); }); describe('Ambient Examples', () => { it('should load and play dark-ambient.json', async () => { const example = loadExample('ambient', 'dark-ambient.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); await controller.stop(); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); }); it('should load and play drone.json', async () => { const example = loadExample('ambient', 'drone.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); await controller.stop(); }); }); describe('Trap Examples', () => { it('should load and play modern-trap.json', async () => { const example = loadExample('trap', 'modern-trap.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); await controller.stop(); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); }); it('should load and play cloud-trap.json', async () => { const example = loadExample('trap', 'cloud-trap.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); await controller.stop(); }); }); describe('Jungle Examples', () => { it('should load and play classic-jungle.json', async () => { const example = loadExample('jungle', 'classic-jungle.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); await controller.stop(); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); }); it('should load and play ragga-jungle.json', async () => { const example = loadExample('jungle', 'ragga-jungle.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); await controller.stop(); }); }); describe('Jazz Examples', () => { it('should load and play bebop.json', async () => { const example = loadExample('jazz', 'bebop.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 1000)); await controller.stop(); const errors = controller.getConsoleErrors(); expect(errors.length).toBe(0); }); it('should load and play modal-jazz.json', async () => { const example = loadExample('jazz', 'modal-jazz.json'); await controller.writePattern(example.pattern); await controller.play(); await new Promise(resolve => setTimeout(resolve, 500)); await controller.stop(); }); }); describe('Audio Analysis Validation', () => { it('should analyze audio from hard-techno example', async () => { const example = loadExample('techno', 'hard-techno.json'); await controller.writePattern(example.pattern); await controller.play(); // Wait for audio analyzer to connect const connected = await controller.waitForAudioConnection(5000); if (!connected) { // Skip test if analyzer doesn't connect (known issue in headless mode) await controller.stop(); console.warn('Audio analyzer did not connect - test skipped'); return; } await new Promise(resolve => setTimeout(resolve, 1500)); const analysis = await controller.analyzeAudio(); expect(analysis).toBeDefined(); expect(analysis.spectrum).toBeDefined(); expect(analysis.spectrum.bands).toBeDefined(); expect(analysis.spectrum.bands.length).toBeGreaterThan(0); await controller.stop(); }); it('should detect tempo from liquid-dnb example', async () => { const example = loadExample('dnb', 'liquid-dnb.json'); await controller.writePattern(example.pattern); await controller.play(); // Wait for audio analyzer to connect const connected = await controller.waitForAudioConnection(5000); if (!connected) { // Skip test if analyzer doesn't connect (known issue in headless mode) await controller.stop(); console.warn('Audio analyzer did not connect - test skipped'); return; } await new Promise(resolve => setTimeout(resolve, 1500)); const tempo = await controller.detectTempo(); expect(tempo).toBeDefined(); expect(tempo.bpm).toBeGreaterThan(0); expect(tempo.bpm).toBeGreaterThanOrEqual(160); expect(tempo.bpm).toBeLessThanOrEqual(180); await controller.stop(); }); it('should detect key from modal-jazz example', async () => { const example = loadExample('jazz', 'modal-jazz.json'); await controller.writePattern(example.pattern); await controller.play(); // Wait for audio analyzer to connect const connected = await controller.waitForAudioConnection(5000); if (!connected) { // Skip test if analyzer doesn't connect (known issue in headless mode) await controller.stop(); console.warn('Audio analyzer did not connect - test skipped'); return; } await new Promise(resolve => setTimeout(resolve, 1500)); const key = await controller.detectKey(); expect(key).toBeDefined(); expect(key.key).toBeDefined(); expect(key.scale).toBeDefined(); expect(key.confidence).toBeGreaterThanOrEqual(0); await controller.stop(); }); }); describe('Pattern Integrity Validation', () => { const allExamples = [ { genre: 'techno', file: 'hard-techno.json' }, { genre: 'techno', file: 'minimal-techno.json' }, { genre: 'house', file: 'deep-house.json' }, { genre: 'house', file: 'tech-house.json' }, { genre: 'dnb', file: 'liquid-dnb.json' }, { genre: 'dnb', file: 'neurofunk.json' }, { genre: 'ambient', file: 'dark-ambient.json' }, { genre: 'ambient', file: 'drone.json' }, { genre: 'trap', file: 'modern-trap.json' }, { genre: 'trap', file: 'cloud-trap.json' }, { genre: 'jungle', file: 'classic-jungle.json' }, { genre: 'jungle', file: 'ragga-jungle.json' }, { genre: 'jazz', file: 'bebop.json' }, { genre: 'jazz', file: 'modal-jazz.json' }, ]; allExamples.forEach(({ genre, file }) => { it(`should have valid metadata: ${genre}/${file}`, () => { const example = loadExample(genre, file); expect(example.name).toBeDefined(); expect(example.genre).toBe(genre); expect(example.pattern).toBeDefined(); expect(example.pattern.length).toBeGreaterThan(0); expect(example.bpm).toBeGreaterThan(0); expect(example.key).toBeDefined(); expect(example.description).toBeDefined(); expect(example.tags).toBeInstanceOf(Array); expect(example.timestamp).toBeDefined(); }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/williamzujkowski/strudel-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server