Files
Oxicloud/tests/e2e/spa/music.spec.ts
T
2026-08-09 13:10:09 +02:00

158 lines
7.4 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import { test, expect } from './coverage-helpers';
import { apiLogin, apiCreateFolder, apiUploadFile } from './helpers';
/**
* Music route — playlist lifecycle, the add-tracks dialog, and the audio player
* (driven with a real WAV fixture so the track shows up in the picker). The
* music page is one of the largest source files.
*/
const TONE_WAV = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'tone.wav'));
test.beforeEach(async ({ page }) => {
await apiLogin(page);
});
function uniq(p: string): string {
return `${p}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
}
/** Create a playlist and return its name; works whether or not others exist. */
async function createPlaylist(page: import('@playwright/test').Page, name: string): Promise<void> {
await page.goto('/music');
const createBtn = page.getByTestId('music-create-playlist-btn');
const emptyBtn = page.getByTestId('music-create-playlist-empty-btn');
await expect(createBtn.or(emptyBtn)).toBeVisible({ timeout: 15_000 });
if (await createBtn.isVisible().catch(() => false)) await createBtn.click();
else await emptyBtn.click();
await page.getByTestId('dialog-host-prompt-input').fill(name);
await page.getByTestId('dialog-host-submit-btn').click();
await expect(page.getByTestId(name)).toBeVisible({ timeout: 15_000 });
}
test('playlist lifecycle: create, toggle public, rename, delete', async ({ page }) => {
const name = uniq('PL');
const renamed = uniq('PLr');
await createPlaylist(page, name);
// Select the playlist → detail panel with its controls.
await page.getByTestId(name).click();
await expect(page.getByTestId('music-rename-playlist-btn')).toBeVisible({ timeout: 15_000 });
await page.getByTestId('music-toggle-public-btn').click().catch(() => {});
await page.getByTestId('music-rename-playlist-btn').click();
await page.getByTestId('dialog-host-prompt-input').fill(renamed);
await page.getByTestId('dialog-host-submit-btn').click();
await expect(page.getByTestId(renamed)).toBeVisible({ timeout: 15_000 });
await page.getByTestId('music-delete-playlist-btn').click();
await page.getByTestId('dialog-host-confirm-btn').click();
await expect(page.getByTestId(renamed)).toHaveCount(0, { timeout: 15_000 });
});
test('reorder playlist tracks by dragging', async ({ page }) => {
const folder = await apiCreateFolder(page, uniq('Audio2'));
const token = `multi${Date.now()}`;
await apiUploadFile(page, { name: `${token}-1.wav`, mimeType: 'audio/wav', body: TONE_WAV }, folder.id);
await apiUploadFile(page, { name: `${token}-2.wav`, mimeType: 'audio/wav', body: TONE_WAV }, folder.id);
const name = uniq('PLr2');
await createPlaylist(page, name);
await page.getByTestId(name).click();
await page.getByTestId('music-add-tracks-btn').click();
await expect(page.getByTestId('music-add-tracks-dialog')).toBeVisible();
await page.getByTestId('music-add-tracks-search-input').fill(token);
await page.getByTestId(`${token}-1.wav`).check({ timeout: 15_000 });
await page.getByTestId(`${token}-2.wav`).check({ timeout: 5_000 });
await page.getByTestId('music-add-tracks-confirm-btn').click();
await expect(page.getByTestId('music-add-tracks-dialog')).toHaveCount(0);
// Drag the first track onto the second to reorder.
const tracks = page.locator('.music-track');
await expect(tracks).toHaveCount(2, { timeout: 15_000 });
await tracks.nth(0).dragTo(tracks.nth(1));
});
test('add-tracks dialog opens, searches, and closes', async ({ page }) => {
const name = uniq('PLt');
await createPlaylist(page, name);
await page.getByTestId(name).click();
await expect(page.getByTestId('music-add-tracks-btn')).toBeVisible({ timeout: 15_000 });
await page.getByTestId('music-add-tracks-btn').click();
await expect(page.getByTestId('music-add-tracks-dialog')).toBeVisible();
await page.getByTestId('music-add-tracks-search-input').fill('song');
await page.getByTestId('music-add-tracks-close-btn').click();
await expect(page.getByTestId('music-add-tracks-dialog')).toHaveCount(0);
});
test('add an audio track and drive the player', async ({ page }) => {
// A real WAV so the backend recognises it as audio and lists it in the picker.
const folder = await apiCreateFolder(page, uniq('Audio'));
const songName = `${uniq('song')}.wav`;
await apiUploadFile(page, { name: songName, mimeType: 'audio/wav', body: TONE_WAV }, folder.id);
const name = uniq('PLp');
await createPlaylist(page, name);
await page.getByTestId(name).click();
await expect(page.getByTestId('music-add-tracks-btn')).toBeVisible({ timeout: 15_000 });
// Open the picker and search by name (an empty query may match nothing).
await page.getByTestId('music-add-tracks-btn').click();
await expect(page.getByTestId('music-add-tracks-dialog')).toBeVisible();
await page.getByTestId('music-add-tracks-search-input').fill(songName.replace('.wav', ''));
const songCheckbox = page.getByTestId(songName);
await expect(songCheckbox).toBeVisible({ timeout: 15_000 });
await songCheckbox.check();
await page.getByTestId('music-add-tracks-confirm-btn').click();
await expect(page.getByTestId('music-add-tracks-dialog')).toHaveCount(0);
// Playlist-level actions on the detail panel.
await page.getByTestId('music-play-all-btn').click({ timeout: 3_000 }).catch(() => {});
await page.getByTestId('music-shuffle-play-btn').click({ timeout: 3_000 }).catch(() => {});
// Play the track and exercise the transport controls.
await page.locator('[data-testid^="music-track-play-"]').first().click({ timeout: 5_000 });
await page.getByTestId('music-player-play-btn').click({ timeout: 3_000 }).catch(() => {});
await page.getByTestId('music-player-shuffle-btn').click({ timeout: 3_000 }).catch(() => {});
await page.getByTestId('music-player-repeat-btn').click({ timeout: 3_000 }).catch(() => {});
await page.getByTestId('music-player-next-btn').click({ timeout: 3_000 }).catch(() => {});
await page.getByTestId('music-player-mute-btn').click({ timeout: 3_000 }).catch(() => {});
// Queue panel — open, remove a queued track, then close.
await page.getByTestId('music-player-queue-toggle-btn').click({ timeout: 3_000 }).catch(() => {});
await page
.locator('[data-testid^="music-queue-remove-"]')
.first()
.click({ timeout: 2_000 })
.catch(() => {});
await page.getByTestId('music-queue-close-btn').click({ timeout: 3_000 }).catch(() => {});
// Edit the playlist description.
await page.getByTestId('music-edit-description-btn').click({ timeout: 3_000 }).catch(() => {});
const descInput = page.getByTestId('dialog-host-prompt-input');
if (await descInput.isVisible().catch(() => false)) {
await descInput.fill('e2e description');
await page.getByTestId('dialog-host-submit-btn').click().catch(() => {});
}
// Manage-shares dialog.
await page.getByTestId('music-manage-shares-btn').click({ timeout: 3_000 }).catch(() => {});
await page.getByTestId('music-shares-close-btn').click({ timeout: 3_000 }).catch(() => {});
// Set a cover image (exercises the cover upload path).
await page.getByTestId('music-set-cover-btn').click({ timeout: 3_000 }).catch(() => {});
await page
.getByTestId('music-cover-input')
.setInputFiles({
name: 'cover.png',
mimeType: 'image/png',
buffer: Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
'base64',
),
})
.catch(() => {});
});