Files
picpeak/tests/e2e/gallery-grid-actions.spec.ts
T

181 lines
6.3 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 || 'PlaywrightGallery123!';
async function ensureGalleryWithPhotos(page) {
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 = `Playwright MCP ${Date.now()}`;
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: 'Playwright Host',
customer_email: 'host@example.com',
admin_email: ADMIN_EMAIL,
password: GALLERY_PASSWORD,
expiration_days: 90,
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,
},
failOnStatusCode: false,
});
expect(createResponse.ok()).toBeTruthy();
const createdEvent = await createResponse.json();
expect(createdEvent?.id).toBeTruthy();
const imagePaths = ['img1.png', 'img2.png'].map((file) =>
path.join(process.cwd(), 'test-assets', file)
);
for (const imagePath of imagePaths) {
const buffer = fs.readFileSync(imagePath);
const uploadResponse = await page.request.post(
`/api/admin/events/${createdEvent.id}/upload`,
{
headers: {
Authorization: `Bearer ${token}`,
},
multipart: {
photos: {
name: path.basename(imagePath),
mimeType: 'image/png',
buffer,
},
category_id: 'individual',
},
failOnStatusCode: false,
}
);
expect(uploadResponse.ok()).toBeTruthy();
}
return {
shareLink: createdEvent.share_link,
slug: createdEvent.slug,
};
}
test.describe('Gallery grid tile quick actions', () => {
test('Each tile: open, download, comment, like with immediate UI', async ({ page }) => {
const { shareLink } = await ensureGalleryWithPhotos(page);
await page.goto(shareLink);
const gallery = page;
await gallery.waitForLoadState('domcontentloaded');
await gallery.waitForURL(/\/gallery\//);
const passwordField = gallery.getByPlaceholder(/gallery password/i).first();
if (await passwordField.count()) {
await passwordField.fill(GALLERY_PASSWORD);
await gallery.getByRole('button', { name: /View Gallery/i }).click();
await gallery.waitForLoadState('networkidle');
}
// Ensure grid tiles rendered
const tiles = gallery.locator('.relative.group');
await expect(tiles.first()).toBeVisible({ timeout: 20000 });
const tileCount = await tiles.count();
expect(tileCount).toBeGreaterThan(0);
// Limit to a few tiles to keep test time sensible
const N = Math.min(tileCount, 3);
for (let i = 0; i < N; i++) {
const tile = tiles.nth(i);
await tile.scrollIntoViewIfNeeded();
// On desktop, actions show on hover
await tile.hover({ force: true });
// Actions should be present
const openBtn = tile.getByRole('button', { name: /View full size/i });
await expect(openBtn).toBeVisible();
const likeBtn = tile.getByRole('button', { name: /Like photo/i }).first();
await expect(likeBtn).toBeVisible();
const commentBtn = tile.getByRole('button', { name: /Comment on photo|Comment/i }).first();
await expect(commentBtn).toBeVisible();
const downloadBtn = tile.getByRole('button', { name: /Download photo/i }).first();
await expect(downloadBtn).toBeVisible();
// Like should toggle to red and indicator appear immediately
const pressedBefore = await likeBtn.getAttribute('aria-pressed');
await likeBtn.click();
await expect.poll(async () => (await likeBtn.getAttribute('aria-pressed')) || '').toContain('true');
// Feedback indicator (title="Liked") should appear on the tile
await expect(tile.locator('[title="Liked"]')).toBeVisible();
// Open lightbox
await openBtn.click();
const closeLightboxBtn = gallery.getByRole('button', { name: /^Close$/i }).first();
await expect(closeLightboxBtn).toBeVisible();
// Close again to continue
await closeLightboxBtn.click();
// Comment quick action should open lightbox with feedback panel visible
await tile.hover({ force: true });
await commentBtn.click();
await expect(gallery.getByRole('button', { name: /Toggle feedback/ })).toBeVisible();
// Ensure feedback panel is visible or open it
const feedbackHeading = gallery.getByRole('heading', { name: /Photo Feedback/i });
if (!(await feedbackHeading.isVisible())) {
await gallery.getByRole('button', { name: /Toggle feedback/ }).click();
}
await expect(feedbackHeading).toBeVisible();
// Comments quick action should surface the feedback tools
const addCommentBtn = gallery.getByRole('button', { name: /Add Comment|Add comment/i });
await expect(addCommentBtn).toBeVisible();
await addCommentBtn.click();
// Allow UI to react without requiring text entry
await gallery.waitForTimeout(250);
// Close lightbox to continue (we do not submit to keep test idempotent)
await closeLightboxBtn.click();
// Download from tile should trigger a browser download event
await tile.hover({ force: true });
const downloadPromise = gallery.waitForEvent('download');
await downloadBtn.click();
const download = await downloadPromise;
expect((await download.path()) !== null).toBeTruthy();
}
});
});