chore: consolidate browser tests and remove temporary artifacts
Some checks failed
Test and deploy / Verify production build (push) Failing after 6m0s
Some checks failed
Test and deploy / Verify production build (push) Failing after 6m0s
This commit is contained in:
53
tests/browser/admin.spec.ts
Normal file
53
tests/browser/admin.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('owner can edit content, create a translated project, upload media, and sign out', async ({ page, request }) => {
|
||||
expect((await request.get('/api/admin/settings')).status()).toBe(401);
|
||||
await page.goto('/admin');
|
||||
await expect(page).toHaveURL(/\/admin\/login$/);
|
||||
await page.getByLabel('Email', { exact: true }).fill('e2e@example.com');
|
||||
await page.getByLabel('Password', { exact: true }).fill('Only-for-isolated-e2e-tests-2026');
|
||||
await page.getByRole('button', { name: 'Sign in', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Site settings', exact: true })).toBeVisible();
|
||||
await page.screenshot({ path: test.info().outputPath('admin-settings.png'), fullPage: true });
|
||||
await page.getByRole('button', { name: 'Profile & sections', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Hero hero', exact: true }).click();
|
||||
const name = page.getByLabel('Name', { exact: true });
|
||||
const oldName = await name.inputValue();
|
||||
await name.fill('An editable portfolio');
|
||||
await page.getByRole('button', { name: 'Save changes', exact: true }).first().click();
|
||||
await expect(page.getByRole('status')).toContainText('Saved');
|
||||
expect(await (await request.get('/')).text()).toContain('An editable portfolio');
|
||||
await page.getByLabel('Name', { exact: true }).fill(oldName);
|
||||
await page.getByRole('button', { name: 'Save changes', exact: true }).first().click();
|
||||
await expect(page.getByRole('status')).toContainText('Saved');
|
||||
|
||||
await page.getByRole('button', { name: 'Languages', exact: true }).click();
|
||||
await page.getByRole('button', { name: '+ Add language', exact: true }).click();
|
||||
await page.getByLabel('Name', { exact: true }).fill('Deutsch');
|
||||
await page.getByLabel('Language code').fill('de');
|
||||
await page.getByRole('button', { name: 'Save changes', exact: true }).first().click();
|
||||
await expect(page.getByRole('status')).toContainText('Saved');
|
||||
const german = await request.get('/de'); expect(german.status()).toBe(200); expect(await german.text()).toContain('lang="de"');
|
||||
|
||||
await page.getByRole('button', { name: 'Projects', exact: true }).click();
|
||||
await page.getByRole('button', { name: '+ Add item', exact: true }).click();
|
||||
const id = `browser-project-${test.info().project.name}`;
|
||||
await page.getByLabel('ID (used in the URL)', { exact: true }).fill(id);
|
||||
await page.getByRole('button', { name: 'Save changes', exact: true }).first().click();
|
||||
await expect(page.getByRole('status')).toContainText('Saved');
|
||||
expect((await request.get(`/de/projects/${id}`)).status()).toBe(200);
|
||||
await page.getByLabel('Visible', { exact: true }).uncheck();
|
||||
await page.getByRole('button', { name: 'Save changes', exact: true }).first().click();
|
||||
await expect(page.getByRole('status')).toContainText('Saved');
|
||||
expect((await request.get(`/projects/${id}`)).status()).toBe(404);
|
||||
page.once('dialog', dialog => dialog.accept());
|
||||
await page.getByRole('button', { name: 'Delete item', exact: true }).click();
|
||||
await expect(page.getByRole('status')).toContainText('Deleted');
|
||||
|
||||
await page.getByRole('button', { name: 'Media library', exact: true }).click();
|
||||
await page.locator('input[type=file]').setInputFiles({ name: 'certificate.pdf', mimeType: 'application/pdf', buffer: Buffer.from('%PDF-1.4\n1 0 obj\n<<>>\nendobj\n%%EOF') });
|
||||
await expect(page.getByRole('link', { name: 'certificate.pdf' }).first()).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Sign out', exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/admin\/login$/);
|
||||
await page.goto('/admin'); await expect(page).toHaveURL(/\/admin\/login$/);
|
||||
});
|
||||
119
tests/browser/project-case-study.spec.ts
Normal file
119
tests/browser/project-case-study.spec.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const caseStudyPath = '/projects/qlockify';
|
||||
|
||||
async function expectDrawerOpen(page: import('@playwright/test').Page) {
|
||||
const dialog = page.locator('dialog[open]');
|
||||
await expect(dialog).toHaveAttribute('data-state', 'open');
|
||||
await expect(dialog).toBeVisible();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
test('a pointer click on the card CTA opens a route-backed drawer and restores the portfolio', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||
await page.evaluate(() => {
|
||||
window.scrollTo({ top: document.body.scrollHeight * 0.35, behavior: 'instant' });
|
||||
});
|
||||
const card = page.getByRole('article', { name: 'Qlockify' });
|
||||
const cta = card.getByRole('link', { name: 'View case study' });
|
||||
// Measure the viewport from which the user actually clicks, after any
|
||||
// automatic scroll needed to bring the link into view.
|
||||
await cta.scrollIntoViewIfNeeded();
|
||||
const portfolioScroll = await page.evaluate(() => window.scrollY);
|
||||
await cta.click();
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`${caseStudyPath}$`));
|
||||
const dialog = await expectDrawerOpen(page);
|
||||
await expect(dialog.getByRole('button', { name: 'Close case study' })).toBeFocused();
|
||||
await expect(card.getByRole('link', { name: /Open the live Qlockify/ })).toHaveAttribute('target', '_blank');
|
||||
await expect(card.getByRole('link', { name: /Open the live Qlockify/ })).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
await expect(page.locator('dialog[open]')).toHaveCount(0);
|
||||
await expect(cta).toBeFocused();
|
||||
await expect.poll(async () => Math.abs(await page.evaluate(() => window.scrollY) - portfolioScroll))
|
||||
.toBeLessThan((page.viewportSize()?.height ?? 800) / 2);
|
||||
});
|
||||
|
||||
test('a shared route opens over the portfolio and adjacent navigation replaces history', async ({ page }) => {
|
||||
await page.goto(caseStudyPath, { waitUntil: 'domcontentloaded' });
|
||||
const dialog = await expectDrawerOpen(page);
|
||||
await expect(page.getByRole('region', { name: 'Products I\'ve Brought to Life' })).toBeAttached();
|
||||
|
||||
const historyLength = await page.evaluate(() => history.length);
|
||||
await dialog.getByRole('link', { name: /Next project Guilan ACE/ }).click();
|
||||
await expect(page).toHaveURL(/\/projects\/guilan-ace$/);
|
||||
await expect(dialog.getByRole('heading', { name: 'Guilan ACE', level: 1 })).toBeVisible();
|
||||
expect(await page.evaluate(() => history.length)).toBe(historyLength);
|
||||
await dialog.getByRole('button', { name: 'Close case study' }).click();
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
});
|
||||
|
||||
test('share fallback, copy feedback, focus containment, and the localized RTL route work', async ({ page, context }) => {
|
||||
// This test exercises the fallback. Native mobile share sheets are outside
|
||||
// the DOM and are covered separately through the supported-share branch.
|
||||
await page.addInitScript(() => Object.defineProperty(navigator, 'share', { configurable: true, value: undefined }));
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
await page.goto('/fa/projects/qlockify', { waitUntil: 'domcontentloaded' });
|
||||
const dialog = await expectDrawerOpen(page);
|
||||
await expect(page.locator('html')).toHaveAttribute('dir', 'rtl');
|
||||
await expect(dialog.getByRole('button', { name: 'بستن مطالعه موردی' })).toBeFocused();
|
||||
|
||||
await dialog.getByRole('button', { name: 'اشتراکگذاری این مطالعه موردی' }).click();
|
||||
const shareDialog = page.getByRole('dialog', { name: 'اشتراکگذاری مطالعه موردی' });
|
||||
await expect(shareDialog).toBeVisible();
|
||||
await shareDialog.getByRole('button', { name: 'کپی پیوند' }).click();
|
||||
await expect(shareDialog.getByText('پیوند کپی شد', { exact: true }).first()).toBeVisible();
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toMatch(/\/fa\/projects\/qlockify$/);
|
||||
|
||||
await shareDialog.getByRole('button', { name: 'بستن گزینههای اشتراکگذاری' }).click();
|
||||
await expect(dialog.getByRole('button', { name: 'اشتراکگذاری این مطالعه موردی' })).toBeFocused();
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
expect(await page.evaluate(() => document.activeElement?.closest('dialog') !== null)).toBe(true);
|
||||
});
|
||||
|
||||
test('mobile uses native sharing when it is available', async ({ page, isMobile }) => {
|
||||
test.skip(!isMobile, 'Native sharing is used on compact layouts');
|
||||
await page.addInitScript(() => Object.defineProperty(navigator, 'share', { configurable: true, value: async (data: ShareData) => { document.documentElement.dataset.sharedUrl = data.url; } }));
|
||||
await page.goto(caseStudyPath, { waitUntil: 'domcontentloaded' });
|
||||
const dialog = await expectDrawerOpen(page);
|
||||
await dialog.getByRole('button', { name: 'Share this case study' }).click();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-shared-url', new RegExp(`${caseStudyPath}$`));
|
||||
});
|
||||
|
||||
test('the gallery serves original media and opens an accessible full-screen viewer', async ({ page, isMobile }) => {
|
||||
await page.goto(caseStudyPath, { waitUntil: 'domcontentloaded' });
|
||||
const drawer = await expectDrawerOpen(page);
|
||||
const stage = drawer.getByRole('button', { name: /Open full-screen image/ });
|
||||
const selectedImage = stage.getByRole('img');
|
||||
await expect(selectedImage).toHaveAttribute('src', '/images/screenshots/qlockify/qlockify-landing.png');
|
||||
|
||||
await stage.click();
|
||||
const viewer = page.getByRole('dialog', { name: 'Full-screen project gallery' });
|
||||
await expect(viewer).toBeVisible();
|
||||
await expect(viewer.getByText('1 of 8')).toBeVisible();
|
||||
if (isMobile) {
|
||||
await viewer.getByRole('button', { name: /^Show image 2:/ }).click();
|
||||
} else {
|
||||
await viewer.getByRole('button', { name: 'Next image' }).click();
|
||||
}
|
||||
await expect(viewer.getByText('2 of 8')).toBeVisible();
|
||||
await viewer.getByRole('button', { name: 'Close full-screen image' }).click();
|
||||
await expect(stage).toBeFocused();
|
||||
});
|
||||
|
||||
test('the drawer stays bottom-anchored with its own scrolling surface', async ({ page }) => {
|
||||
await page.goto(caseStudyPath, { waitUntil: 'domcontentloaded' });
|
||||
const dialog = await expectDrawerOpen(page);
|
||||
const sheet = dialog.locator(':scope > div');
|
||||
const scrollArea = sheet.locator(':scope > div:last-child');
|
||||
const box = await sheet.boundingBox();
|
||||
const viewport = page.viewportSize();
|
||||
expect(box).not.toBeNull();
|
||||
expect(viewport).not.toBeNull();
|
||||
expect(Math.abs((box?.y ?? 0) + (box?.height ?? 0) - (viewport?.height ?? 0))).toBeLessThanOrEqual(2);
|
||||
expect(await page.locator('html').evaluate((element) => getComputedStyle(element).overflow)).toBe('hidden');
|
||||
expect(await scrollArea.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user