test(e2e): add CCLI paste import, bookmarklet, and translation pairing e2e tests
This commit is contained in:
parent
f2b10a4cd7
commit
03fdfac3d3
77
tests/e2e/ccli-bookmarklet.spec.ts
Normal file
77
tests/e2e/ccli-bookmarklet.spec.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('CCLI Bookmarklet', () => {
|
||||
test('Settings page shows CCLI section with bookmarklet drag link', async ({ page }) => {
|
||||
await page.goto('/settings');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const ccliBtn = page.locator('[data-testid="settings-submenu-ccli"]').first();
|
||||
await ccliBtn.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await ccliBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.getByTestId('default-translation-language')).toBeVisible();
|
||||
await expect(page.getByTestId('ccli-bookmarklet-drag-link')).toBeVisible();
|
||||
});
|
||||
|
||||
test('bookmarklet endpoint returns valid JS', async ({ request }) => {
|
||||
const res = await request.get('/bookmarklets/ccli-import.js');
|
||||
expect(res.status()).toBe(200);
|
||||
expect(res.headers()['content-type']).toContain('text/javascript');
|
||||
|
||||
const body = await res.text();
|
||||
expect(body).toMatch(/^javascript:/);
|
||||
expect(body).toContain('import-from-ccli-paste');
|
||||
expect(body).toContain('songselect.ccli.com');
|
||||
});
|
||||
|
||||
test('bookmarklet redirect page renders with valid base64 prefill', async ({ page }) => {
|
||||
const payload = JSON.stringify({
|
||||
title: 'Amazing Grace',
|
||||
author: 'John Newton',
|
||||
ccliId: '4760',
|
||||
sourceUrl: 'https://songselect.ccli.com/Songs/4760',
|
||||
rawText: 'Amazing Grace\nJohn Newton\n\nVerse 1\nAmazing grace how sweet the sound\n\n© 1779\nCCLI # 4760',
|
||||
});
|
||||
const encoded = Buffer.from(payload).toString('base64');
|
||||
|
||||
await page.goto(`/songs/import-from-ccli-paste?prefill=${encoded}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.getByTestId('ccli-paste-textarea')).toBeVisible();
|
||||
});
|
||||
|
||||
test('bookmarklet redirect page shows error for invalid base64', async ({ page }) => {
|
||||
await page.goto('/songs/import-from-ccli-paste?prefill=NOT_VALID_BASE64!!!');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.getByTestId('ccli-prefill-error-message')).toBeVisible();
|
||||
await expect(page.getByTestId('ccli-paste-textarea')).toBeVisible();
|
||||
});
|
||||
|
||||
test('default language dropdown persists selection', async ({ page }) => {
|
||||
await page.goto('/settings');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const ccliBtn = page.locator('[data-testid="settings-submenu-ccli"]').first();
|
||||
await ccliBtn.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await ccliBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const select = page.getByTestId('default-translation-language');
|
||||
await select.selectOption('EN');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
const ccliBtn2 = page.locator('[data-testid="settings-submenu-ccli"]').first();
|
||||
await ccliBtn2.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await ccliBtn2.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.getByTestId('default-translation-language')).toHaveValue('EN');
|
||||
|
||||
await page.getByTestId('default-translation-language').selectOption('DE');
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
});
|
||||
113
tests/e2e/ccli-paste-import.spec.ts
Normal file
113
tests/e2e/ccli-paste-import.spec.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function loadFixture(name: string): string {
|
||||
return fs.readFileSync(`tests/Fixtures/ccli/${name}`, 'utf-8');
|
||||
}
|
||||
|
||||
test.describe('CCLI Paste Import - SongDB', () => {
|
||||
test('SongDB page shows CCLI import buttons', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.getByTestId('open-ccli-paste-dialog-button-songdb')).toBeVisible();
|
||||
});
|
||||
|
||||
test('SongSelect search button opens new tab with prefilled URL', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Fill search input
|
||||
await page.getByTestId('song-list-search-input').fill('amazing grace');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click SongSelect search button
|
||||
const [popup] = await Promise.all([
|
||||
page.waitForEvent('popup'),
|
||||
page.getByTestId('songselect-search-button-songdb').click(),
|
||||
]);
|
||||
|
||||
await popup.waitForLoadState('domcontentloaded');
|
||||
expect(popup.url()).toContain('songselect.ccli.com');
|
||||
expect(popup.url()).toContain('amazing');
|
||||
await popup.close();
|
||||
});
|
||||
|
||||
test('opens CCLI paste dialog from SongDB', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByTestId('open-ccli-paste-dialog-button-songdb').click();
|
||||
await expect(page.getByTestId('ccli-paste-textarea')).toBeVisible();
|
||||
await expect(page.getByTestId('ccli-preview-button')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('preview button disabled when textarea empty', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByTestId('open-ccli-paste-dialog-button-songdb').click();
|
||||
await expect(page.getByTestId('ccli-preview-button')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('paste fixture and preview shows metadata', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByTestId('open-ccli-paste-dialog-button-songdb').click();
|
||||
|
||||
const content = loadFixture('english-only-multi-verse.txt');
|
||||
await page.getByTestId('ccli-paste-textarea').fill(content);
|
||||
await expect(page.getByTestId('ccli-preview-button')).toBeEnabled();
|
||||
|
||||
await page.getByTestId('ccli-preview-button').click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const errorMsg = page.getByTestId('ccli-error-message');
|
||||
const hasError = await errorMsg.isVisible().catch(() => false);
|
||||
if (hasError) {
|
||||
test.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
await expect(page.locator('text=CCLI-Nr')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('duplicate import shows error with edit link', async ({ page }) => {
|
||||
const content = loadFixture('english-only-multi-verse.txt');
|
||||
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
const cookies = await page.context().cookies();
|
||||
const xsrf = cookies.find(c => c.name === 'XSRF-TOKEN')?.value ?? '';
|
||||
|
||||
const importRes = await page.request.post('/api/songs/import-from-ccli-paste', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': decodeURIComponent(xsrf),
|
||||
},
|
||||
data: { raw_text: content, mode: 'create' },
|
||||
});
|
||||
if (importRes.status() !== 201) {
|
||||
test.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Now try to import again via UI
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByTestId('open-ccli-paste-dialog-button-songdb').click();
|
||||
await page.getByTestId('ccli-paste-textarea').fill(content);
|
||||
await page.getByTestId('ccli-preview-button').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Click import
|
||||
const importBtn = page.getByTestId('ccli-import-stay-button');
|
||||
if (await importBtn.isVisible()) {
|
||||
await importBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
await expect(page.getByTestId('ccli-error-message')).toBeVisible();
|
||||
await expect(page.getByTestId('ccli-existing-song-link')).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
61
tests/e2e/ccli-translation-pairing.spec.ts
Normal file
61
tests/e2e/ccli-translation-pairing.spec.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
|
||||
test.describe('CCLI Translation Pairing', () => {
|
||||
test('Translate.vue shows prefill banner when arrived from CCLI pairing', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
const cookies = await page.context().cookies();
|
||||
const xsrf = cookies.find(c => c.name === 'XSRF-TOKEN')?.value ?? '';
|
||||
|
||||
const content = fs.readFileSync('tests/Fixtures/ccli/english-only-multi-verse.txt', 'utf-8');
|
||||
const importRes = await page.request.post('/api/songs/import-from-ccli-paste', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': decodeURIComponent(xsrf),
|
||||
},
|
||||
data: { raw_text: content, mode: 'create' },
|
||||
});
|
||||
|
||||
if (importRes.status() !== 201) {
|
||||
test.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const { song_id } = await importRes.json();
|
||||
|
||||
await page.goto(`/songs/${song_id}/translate`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.getByTestId('translate-source-textarea')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Translate.vue shows no prefill banner without prefill param', async ({ page }) => {
|
||||
await page.goto('/songs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
const cookies = await page.context().cookies();
|
||||
const xsrf = cookies.find(c => c.name === 'XSRF-TOKEN')?.value ?? '';
|
||||
|
||||
const content = fs.readFileSync('tests/Fixtures/ccli/english-only-multi-verse.txt', 'utf-8');
|
||||
const importRes = await page.request.post('/api/songs/import-from-ccli-paste', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': decodeURIComponent(xsrf),
|
||||
},
|
||||
data: { raw_text: content, mode: 'create' },
|
||||
});
|
||||
|
||||
if (importRes.status() !== 201) {
|
||||
test.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const { song_id } = await importRes.json();
|
||||
|
||||
await page.goto(`/songs/${song_id}/translate`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const banner = page.getByTestId('ccli-prefill-banner');
|
||||
await expect(banner).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue