Files
picpeak/tests/e2e/external-media-gallery.spec.ts

197 lines
6.8 KiB
TypeScript

import { test, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@example.com';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'Admin!234';
const GALLERY_PASSWORD = process.env.GALLERY_PASSWORD || 'ExternalMediaPass!1';
async function createExternalGallery(page) {
const externalRoot = path.join(process.cwd(), 'storage', 'external-media', 'picsum-demo', 'individual');
if (!fs.existsSync(externalRoot)) {
fs.mkdirSync(externalRoot, { recursive: true });
}
const sampleImages = ['img1.png', 'img2.png'];
for (const imageName of sampleImages) {
const source = path.join(process.cwd(), 'test-assets', imageName);
const target = path.join(externalRoot, imageName);
if (!fs.existsSync(target)) {
fs.copyFileSync(source, target);
}
}
const loginResponse = await page.request.post('/api/auth/admin/login', {
data: {
username: ADMIN_EMAIL,
password: ADMIN_PASSWORD,
},
failOnStatusCode: false,
});
expect(loginResponse.ok()).toBeTruthy();
const { token } = await loginResponse.json();
expect(token).toBeTruthy();
const eventName = `External Media Playwright ${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const eventDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
.toISOString()
.slice(0, 10);
const createResponse = await page.request.post('/api/admin/events', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: {
event_type: 'wedding',
event_name: eventName,
event_date: eventDate,
customer_name: 'External Host',
customer_email: 'host@example.com',
admin_email: ADMIN_EMAIL,
password: GALLERY_PASSWORD,
expiration_days: 30,
allow_user_uploads: false,
allow_downloads: true,
disable_right_click: false,
watermark_downloads: false,
feedback_enabled: true,
allow_ratings: true,
allow_likes: true,
allow_comments: true,
allow_favorites: true,
require_name_email: false,
moderate_comments: false,
show_feedback_to_guests: true,
source_mode: 'reference',
external_path: 'picsum-demo'
},
failOnStatusCode: false,
});
if (!createResponse.ok()) {
const bodyText = await createResponse.text();
throw new Error(`Failed to create event: ${createResponse.status()} ${bodyText}`);
}
const createdEvent = await createResponse.json();
expect(createdEvent?.id).toBeTruthy();
const importResponse = await page.request.post(`/api/admin/external-media/events/${createdEvent.id}/import-external`, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: {
external_path: 'picsum-demo',
recursive: true,
},
failOnStatusCode: false,
});
if (!importResponse.ok()) {
const bodyText = await importResponse.text();
throw new Error(`Failed to import external media: ${importResponse.status()} ${bodyText}`);
}
const importBody = await importResponse.json();
expect(importBody.imported).toBeGreaterThan(0);
await page.request.put(`/api/admin/feedback/events/${createdEvent.id}/feedback-settings`, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: {
feedback_enabled: true,
allow_ratings: true,
allow_likes: true,
allow_comments: true,
allow_favorites: true,
require_name_email: false,
moderate_comments: false,
show_feedback_to_guests: true,
},
});
return {
shareLink: createdEvent.share_link,
slug: createdEvent.slug,
};
}
test.describe('External media gallery behavior', () => {
test.describe.configure({ mode: 'serial' });
test('Maintains session and favorites after reload', async ({ page, context }) => {
if (test.info().project.name.includes('mobile')) {
test.skip('Mobile viewport handling requires manual verification.');
}
const { shareLink, slug } = await createExternalGallery(page);
await page.goto(shareLink);
await page.waitForLoadState('domcontentloaded');
const passwordField = page.getByPlaceholder(/gallery password/i).first();
if (await passwordField.count()) {
await passwordField.fill(GALLERY_PASSWORD);
const viewButton = page.getByRole('button', { name: /View Gallery/i });
if (await viewButton.count()) {
await viewButton.click({ noWaitAfter: true, timeout: 2000 });
}
}
const tiles = page.locator('.relative.group');
await expect(tiles.first()).toBeVisible({ timeout: 20000 });
const initialTileCount = await tiles.count();
expect(initialTileCount).toBeGreaterThan(0);
const firstTile = tiles.first();
await firstTile.scrollIntoViewIfNeeded();
await firstTile.getByRole('button', { name: /View full size/i }).click();
await page.evaluate(() => {
const toggle = document.querySelector('[aria-label="Toggle feedback"]');
if (toggle instanceof HTMLElement) toggle.click();
});
const favoritesButtonInLightbox = page.getByRole('button', { name: /Add to favorites|Remove from favorites/ }).first();
await expect(favoritesButtonInLightbox).toBeVisible();
const ariaLabel = await favoritesButtonInLightbox.getAttribute('aria-label');
const isAlreadyFavorited = ariaLabel ? /Remove from favorites/i.test(ariaLabel) : false;
const refetchPromise = page.waitForResponse((res) => {
return res.request().method() === 'GET' && res.url().includes(`/api/gallery/${slug}/photos`);
});
if (!isAlreadyFavorited) {
const favResponsePromise = page.waitForResponse((res) => {
return res.request().method() === 'POST' && res.url().includes(`/api/gallery/${slug}/photos/`);
});
await favoritesButtonInLightbox.click();
await Promise.all([favResponsePromise, refetchPromise]);
} else {
await refetchPromise;
}
await page.getByRole('button', { name: 'Close', exact: true }).click();
await page.getByRole('button', { name: 'Favorited' }).click();
await expect(page.locator('.relative.group')).toHaveCount(1, { timeout: 15000 });
await page.reload();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/gallery\//);
await expect(page.locator('.relative.group').first()).toBeVisible();
await page.getByRole('button', { name: 'Favorited' }).click();
await expect(page.locator('.relative.group')).toHaveCount(1, { timeout: 15000 });
await page.getByRole('button', { name: 'All', exact: true }).click();
await expect(page.locator('.relative.group')).toHaveCount(initialTileCount);
const cookies = await context.cookies();
expect(cookies.some((cookie) => cookie.name === 'gallery_token')).toBeTruthy();
});
});