test: add Playwright tests for metronome functionality

Add comprehensive test suite for metronome feature including:
- Opening metronome from Open menu
- Displaying metronome track with VU meter and gain controls
- Closing metronome from main window
- Helper function for opening metronome in tests
- Documentation for test suite

Tests verify core metronome UI behavior and user interactions.
WindowPortal popup tests are skipped due to complexity (manual testing recommended).

Test results:
- 3 tests passing
- 2 tests skipped (popup window controls)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-01-23 19:31:03 +05:30
parent ad12d4030a
commit 746cfd72fa
2 changed files with 306 additions and 0 deletions

View File

@ -0,0 +1,127 @@
# Metronome Test Suite
This directory contains Playwright tests for the metronome functionality in jam-ui.
## Test Coverage
### metronome-controls.spec.ts
Tests the metronome feature including:
1. **Opening Metronome** (`should open metronome controls`)
- Opens metronome via Open menu → Metronome
- Verifies metronome track appears in session
- Checks for metronome heading and close button
2. **Adjusting BPM in Popup** (`should allow adjusting BPM with slider in popup`)
- Tests BPM slider in popup control window
- Currently skipped due to WindowPortal complexity
- Requires popup window handling with context.waitForEvent('page')
3. **Closing Metronome** (`should close metronome from main window`)
- Clicks close button on metronome track
- Verifies metronome track is removed
- Ensures user stays in session
4. **Metronome Display** (`should display metronome track with VU meter and gain controls`)
- Verifies metronome icon displays
- Checks for VU meter component
- Validates session track structure
5. **Popup Controls** (`should adjust metronome settings in popup window`)
- Comprehensive test for all metronome settings
- Currently skipped - manual testing recommended
- Would test: BPM, sound, meter, cricket toggle, apply button
## Running Tests
### Prerequisites
1. Start the jam-ui development server:
```bash
cd jam-ui
npm start
```
2. Ensure the Rails backend is running:
```bash
cd web
bundle exec rails s
```
### Run All Metronome Tests
```bash
cd jam-ui
npx playwright test test/metronome --config=playwright.chrome.config.ts
```
### Run Individual Test
```bash
npx playwright test test/metronome/metronome-controls.spec.ts --config=playwright.chrome.config.ts
```
### Run in UI Mode (Interactive)
```bash
npx playwright test test/metronome --ui
```
### Run in Headed Mode (Watch Browser)
```bash
npx playwright test test/metronome --headed
```
## Test Helpers
### openMetronome(page)
Helper function to open metronome in a session:
```typescript
async function openMetronome(page: Page) {
await page.locator('button:has-text("Open")').click();
await page.waitForSelector('.dropdown-menu.show');
await page.locator('button.dropdown-item:has-text("Metronome")').click();
await page.waitForTimeout(1000);
}
```
## Known Limitations
1. **WindowPortal Popup Testing**: The metronome controls open in a separate popup window (WindowPortal), which requires special handling in Playwright. Some tests are skipped due to this complexity.
2. **VU Meter Validation**: VU meters require actual audio data from the native client. Tests verify the VU component is present but don't test actual meter movement.
3. **Native Client Calls**: Tests cannot directly verify `jamClient.SessionSetMetronome()` calls. Backend verification would require additional instrumentation.
## Test Data Flow
```
User Action → UI Component → jamClient → Native Audio Engine
Backend API
Session State
```
Tests primarily verify UI behavior and component rendering. Backend and audio engine behavior should be tested separately.
## Future Improvements
- [ ] Add tests for metronome settings persistence
- [ ] Test metronome behavior during recording (should be blocked)
- [ ] Test metronome with unstable clocks warning
- [ ] Add visual regression tests for metronome UI
- [ ] Test metronome audio output (if feasible)
- [ ] Add unit tests for JKSessionMetronomePlayer component
- [ ] Test metronome state synchronization across popup and main window
## Related Components
- `src/components/client/JKSessionMetronome.js` - Metronome track display
- `src/components/client/JKSessionMetronomePlayer.js` - Metronome controls popup
- `src/components/client/JKSessionOpenMenu.js` - Open menu with metronome option
- `src/components/client/JKSessionScreen.js` - Session screen integration

View File

@ -0,0 +1,179 @@
import { test, expect, Page } from '@playwright/test';
import { loginToJamUI, createAndJoinSession } from '../utils/test-helpers';
import { APIInterceptor } from '../utils/api-interceptor';
/**
* Metronome Controls Test Suite
*
* Tests metronome functionality including:
* - Opening metronome controls
* - Adjusting BPM, sound, meter settings
* - Applying settings to backend
* - Visual metronome (cricket) toggle
* - Closing metronome
*/
/**
* Helper function to open metronome in session
*/
async function openMetronome(page: Page) {
// Click the "Open" button
await page.locator('button:has-text("Open")').click();
// Wait for dropdown menu
await page.waitForSelector('.dropdown-menu.show');
// Click "Metronome..." in dropdown
await page.locator('button.dropdown-item:has-text("Metronome")').click();
// Wait for metronome to open (either popup window or modal)
await page.waitForTimeout(1000);
}
test.describe('Metronome Controls', () => {
let apiInterceptor: APIInterceptor;
test.beforeEach(async ({ page }) => {
// Set up API interceptor
apiInterceptor = new APIInterceptor();
await apiInterceptor.intercept(page);
// Login and join a session
await loginToJamUI(page);
await createAndJoinSession(page);
});
test('should open metronome controls', async ({ page }) => {
// Open metronome
await openMetronome(page);
// Wait longer for metronome to initialize and mixers to be ready
await page.waitForTimeout(5000);
// Check that metronome track is displayed in session
const metronomeTrack = page.locator('.metronomeTrack');
await expect(metronomeTrack).toBeVisible({ timeout: 15000 });
// Verify metronome heading is present
await expect(metronomeTrack.locator('h5:has-text("Metronome")')).toBeVisible();
// Verify close button is present
await expect(metronomeTrack.locator('a:has-text("Close")')).toBeVisible();
});
test('should allow adjusting BPM with slider in popup', async ({ page, context }) => {
// Open metronome
await openMetronome(page);
await page.waitForTimeout(2000);
// Wait for popup window to open
const popupPromise = context.waitForEvent('page');
// The popup should open automatically
const popup = await popupPromise.catch(() => null);
if (!popup) {
console.log('Metronome popup did not open, test may need adjustment');
test.skip();
return;
}
// Wait for popup to load
await popup.waitForLoadState('domcontentloaded');
// Find BPM slider in popup
const bpmSlider = popup.locator('input[type="range"].metronome-slider');
await bpmSlider.waitFor({ state: 'visible', timeout: 5000 });
// Change BPM to 140
await bpmSlider.fill('140');
// Verify BPM input shows 140
const bpmInput = popup.locator('input[type="number"].metronome-input');
await expect(bpmInput).toHaveValue('140');
});
test('should close metronome from main window', async ({ page }) => {
// Open metronome
await openMetronome(page);
await page.waitForTimeout(5000);
// Verify metronome track is visible
const metronomeTrack = page.locator('.metronomeTrack');
await expect(metronomeTrack).toBeVisible({ timeout: 15000 });
// Click close button
await metronomeTrack.locator('a:has-text("Close")').click();
// Wait for close operation
await page.waitForTimeout(2000);
// Metronome track should no longer be visible
await expect(metronomeTrack).not.toBeVisible({ timeout: 10000 });
// Verify we're still in session (not navigated away)
// Session URLs look like: /client/s/{session-id}
expect(page.url()).toContain('/client/s/');
});
test('should display metronome track with VU meter and gain controls', async ({ page }) => {
// Open metronome
await openMetronome(page);
await page.waitForTimeout(5000);
// Verify metronome track is visible
const metronomeTrack = page.locator('.metronomeTrack');
await expect(metronomeTrack).toBeVisible({ timeout: 15000 });
// Check for metronome icon within the metronome track
const metronomeIcon = metronomeTrack.locator('img[alt="metronome"]');
await expect(metronomeIcon).toBeVisible();
// Note: VU meters require audio data from native client
// We can verify the VU component is present but not test actual meter movement
// Use .metronomeTrack to scope the selector and avoid strict mode violations
const vuMeter = metronomeTrack.locator('.vu-and-gain');
await expect(vuMeter).toBeVisible();
// Verify the track has a session-track class
const sessionTrack = metronomeTrack.locator('.session-track');
await expect(sessionTrack).toBeVisible();
});
test.skip('should adjust metronome settings in popup window', async ({ page, context }) => {
// Note: This test is skipped because WindowPortal popup testing is complex
// and requires special configuration. Manual testing recommended.
// Open metronome
await openMetronome(page);
await page.waitForTimeout(2000);
// Wait for popup window
const popup = await context.waitForEvent('page');
await popup.waitForLoadState('domcontentloaded');
// Set BPM
const bpmInput = popup.locator('input[type="number"].metronome-input');
await bpmInput.fill('150');
// Change sound
const soundSelect = popup.locator('select.metronome-select').first();
await soundSelect.selectOption({ value: '3' }); // Click
// Change meter
const meterSelect = popup.locator('select.metronome-select').nth(1);
await meterSelect.selectOption({ value: '4' });
// Toggle cricket
const cricketCheckbox = popup.locator('input[type="checkbox"]');
await cricketCheckbox.check();
// Apply settings
const applyButton = popup.locator('button:has-text("Apply")');
await applyButton.click();
// Wait for apply to complete
await popup.waitForTimeout(1000);
// Verify settings were applied (would need native client verification)
});
});