546 lines
26 KiB
TypeScript
546 lines
26 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { login, TEST_ORG_SLUG, TEST_EMAIL, waitForHydration, navigateTo } from './helpers';
|
|
|
|
// ─── File Management ────────────────────────────────────────────────────────
|
|
|
|
test.describe('File Management', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/documents`);
|
|
});
|
|
|
|
test('should load files page with header and New button', async ({ page }) => {
|
|
await expect(page.getByRole('heading', { name: 'Files' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'New' })).toBeVisible();
|
|
});
|
|
|
|
test('should open Create New modal with type selectors', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await expect(modal.getByText('Create New')).toBeVisible();
|
|
// Type selector buttons
|
|
await expect(modal.getByText('Document')).toBeVisible();
|
|
await expect(modal.getByText('Folder')).toBeVisible();
|
|
await expect(modal.getByText('Kanban')).toBeVisible();
|
|
// Name input and Create button
|
|
await expect(modal.getByText('Name')).toBeVisible();
|
|
await expect(modal.getByRole('button', { name: 'Create' })).toBeVisible();
|
|
await expect(modal.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
|
});
|
|
|
|
test('should create a folder and see it in the file list', async ({ page }) => {
|
|
const folderName = `Test Folder ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
// Select Folder type
|
|
await modal.getByText('Folder').click();
|
|
// Fill name
|
|
await modal.getByPlaceholder('Folder name').fill(folderName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
// Modal should close
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
// Folder should appear in the file list
|
|
await expect(page.getByText(folderName)).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should create a document and navigate to editor', async ({ page }) => {
|
|
const docName = `Test Doc ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
// Document type is default
|
|
await modal.getByPlaceholder('Document name').fill(docName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
// Should navigate to the file editor page
|
|
await page.waitForURL(new RegExp(`/${TEST_ORG_SLUG}/documents/file/`), { timeout: 10000 });
|
|
await expect(page.getByText(docName).first()).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should create a kanban board from files and navigate to it', async ({ page }) => {
|
|
const boardName = `Test Board ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
// Select Kanban type
|
|
await modal.getByText('Kanban').click();
|
|
await modal.getByPlaceholder('Kanban board name').fill(boardName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
// Should navigate to the kanban file page
|
|
await page.waitForURL(new RegExp(`/${TEST_ORG_SLUG}/documents/file/`), { timeout: 10000 });
|
|
await waitForHydration(page);
|
|
await expect(page.getByText(boardName).first()).toBeVisible({ timeout: 5000 });
|
|
// Default columns should be created
|
|
await expect(page.getByText('To Do').first()).toBeVisible({ timeout: 10000 });
|
|
await expect(page.getByText('In Progress').first()).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('Done').first()).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should navigate into a folder and see breadcrumbs', async ({ page }) => {
|
|
// First create a folder
|
|
const folderName = `Nav Folder ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByText('Folder').click();
|
|
await modal.getByPlaceholder('Folder name').fill(folderName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
// Click the folder button to navigate into it
|
|
const folderBtn = page.locator('button', { hasText: folderName }).first();
|
|
await folderBtn.scrollIntoViewIfNeeded();
|
|
await folderBtn.click();
|
|
await page.waitForURL(new RegExp(`/${TEST_ORG_SLUG}/documents/folder/`), { timeout: 10000 });
|
|
await waitForHydration(page);
|
|
// Breadcrumb should show Home > FolderName
|
|
const breadcrumb = page.locator('nav');
|
|
await expect(breadcrumb.getByText('Home')).toBeVisible({ timeout: 3000 });
|
|
await expect(breadcrumb.getByText(folderName)).toBeVisible({ timeout: 3000 });
|
|
});
|
|
|
|
test('should rename a file via right-click context menu', async ({ page }) => {
|
|
// Create a document first
|
|
const originalName = `Rename Me ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByText('Folder').click();
|
|
await modal.getByPlaceholder('Folder name').fill(originalName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText(originalName).first()).toBeVisible({ timeout: 5000 });
|
|
|
|
// Right-click to open context menu
|
|
await page.getByText(originalName).first().click({ button: 'right' });
|
|
// Context menu should appear - find the Rename button inside the fixed z-50 context menu
|
|
const contextMenuPanel = page.locator('.fixed.z-50.bg-night');
|
|
await expect(contextMenuPanel).toBeVisible({ timeout: 3000 });
|
|
const renameBtn = contextMenuPanel.locator('button', { hasText: 'Rename' });
|
|
await expect(renameBtn).toBeVisible({ timeout: 3000 });
|
|
|
|
// Click Rename
|
|
await renameBtn.click();
|
|
const renameModal = page.getByRole('dialog');
|
|
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
|
|
|
// Clear and type new name
|
|
const newName = `Renamed ${Date.now()}`;
|
|
const nameInput = renameModal.locator('input');
|
|
await nameInput.clear();
|
|
await nameInput.fill(newName);
|
|
await renameModal.getByRole('button', { name: 'Save' }).click();
|
|
await expect(renameModal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// New name should be visible
|
|
await expect(page.getByText(newName)).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should toggle between grid and list view', async ({ page }) => {
|
|
// Default is grid view - look for the toggle button
|
|
const toggleBtn = page.locator('button[title="Toggle view"]');
|
|
await expect(toggleBtn).toBeVisible();
|
|
// Click to switch to list view
|
|
await toggleBtn.click();
|
|
// The file list container should use list layout (flex-col)
|
|
const listContainer = page.locator('div[role="list"].flex.flex-col');
|
|
await expect(listContainer).toBeVisible({ timeout: 3000 });
|
|
// Click again to switch back to grid
|
|
await toggleBtn.click();
|
|
const gridContainer = page.locator('div[role="list"].grid');
|
|
await expect(gridContainer).toBeVisible({ timeout: 3000 });
|
|
});
|
|
});
|
|
|
|
// ─── Kanban Board ───────────────────────────────────────────────────────────
|
|
|
|
test.describe('Kanban Board Page', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/kanban`);
|
|
});
|
|
|
|
test('should load kanban page with header', async ({ page }) => {
|
|
await expect(page.getByRole('heading', { name: 'Kanban' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'New' })).toBeVisible();
|
|
});
|
|
|
|
test('should open Create Board modal and create a board', async ({ page }) => {
|
|
const boardName = `PW Board ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await expect(modal.getByText('Create Board')).toBeVisible();
|
|
await modal.getByPlaceholder('e.g. Sprint 1').fill(boardName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
// Board should be selected and show default columns
|
|
await expect(page.getByText('To Do')).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('In Progress')).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('Done')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should show board selector when multiple boards exist', async ({ page }) => {
|
|
// Create two boards
|
|
for (const name of [`Board A ${Date.now()}`, `Board B ${Date.now()}`]) {
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByPlaceholder('e.g. Sprint 1').fill(name);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
// Wait for board to load
|
|
await expect(page.getByText('To Do')).toBeVisible({ timeout: 5000 });
|
|
}
|
|
// Board selector pills should be visible
|
|
const boardButtons = page.locator('button.rounded-\\[32px\\]');
|
|
const count = await boardButtons.count();
|
|
expect(count).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|
|
|
|
// ─── Settings: Members & Roles ──────────────────────────────────────────────
|
|
|
|
test.describe('Settings Page - Members', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/settings`);
|
|
});
|
|
|
|
test('should load settings page with tabs', async ({ page }) => {
|
|
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'General' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Members' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Roles' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Integrations' })).toBeVisible();
|
|
});
|
|
|
|
test('should switch to Members tab and show team members', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Members' }).click();
|
|
// Team Members heading should show a non-zero count
|
|
const heading = page.locator('h2').filter({ hasText: 'Team Members' });
|
|
await expect(heading).toBeVisible({ timeout: 5000 });
|
|
await expect(heading).not.toHaveText('Team Members (0)');
|
|
await expect(page.getByRole('button', { name: 'Invite Member' })).toBeVisible();
|
|
// The test user's email should appear in the members list (scope to main to avoid sidebar match)
|
|
const main = page.locator('main');
|
|
await expect(main.getByText(TEST_EMAIL).first()).toBeVisible({ timeout: 5000 });
|
|
// Each member should have a role badge
|
|
await expect(main.getByText('owner').first()).toBeVisible();
|
|
});
|
|
|
|
test('should open Invite Member modal with email and role fields', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Members' }).click();
|
|
await expect(page.getByRole('button', { name: 'Invite Member' })).toBeVisible({ timeout: 3000 });
|
|
await page.getByRole('button', { name: 'Invite Member' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await expect(modal.getByText('Invite Member')).toBeVisible();
|
|
await expect(modal.getByText('Email address')).toBeVisible();
|
|
await expect(modal.getByText('Role')).toBeVisible();
|
|
await expect(modal.getByRole('button', { name: 'Send Invite' })).toBeVisible();
|
|
await expect(modal.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
|
});
|
|
|
|
test('should send an invite and show it in pending invites', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Members' }).click();
|
|
await page.getByRole('button', { name: 'Invite Member' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
|
|
const testInviteEmail = `playwright-test-${Date.now()}@example.com`;
|
|
await modal.getByPlaceholder('colleague@example.com').fill(testInviteEmail);
|
|
await modal.getByRole('button', { name: 'Send Invite' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Pending invite should appear
|
|
await expect(page.getByText('Pending Invites')).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText(testInviteEmail).first()).toBeVisible({ timeout: 5000 });
|
|
|
|
// Clean up: cancel the invite - find the row containing the email, then its close button
|
|
const inviteRow = page.locator('.bg-light\\/5').filter({ hasText: testInviteEmail });
|
|
await inviteRow.getByTitle('Cancel invite').click();
|
|
await expect(page.getByText(testInviteEmail)).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should show pending invites section when invites exist', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Members' }).click();
|
|
await expect(page.getByRole('heading', { name: /Team Members/ })).toBeVisible({ timeout: 5000 });
|
|
// Check if Pending Invites section exists (from previous test runs or this session)
|
|
const hasPending = await page.getByText('Pending Invites').isVisible().catch(() => false);
|
|
if (hasPending) {
|
|
// Each invite should have copy and cancel icon buttons
|
|
await expect(page.getByTitle('Cancel invite').first()).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Settings Page - Roles', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/settings`);
|
|
});
|
|
|
|
test('should switch to Roles tab and show existing roles', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Roles' }).click();
|
|
await expect(page.getByText('Roles', { exact: true }).first()).toBeVisible({ timeout: 3000 });
|
|
await expect(page.getByRole('button', { name: 'Create Role' })).toBeVisible();
|
|
// System roles should be visible
|
|
await expect(page.getByText('Owner')).toBeVisible({ timeout: 3000 });
|
|
});
|
|
|
|
test('should open Create Role modal with name, color, and permissions', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Roles' }).click();
|
|
await page.getByRole('button', { name: 'Create Role' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await expect(modal.getByText('Create Role')).toBeVisible();
|
|
await expect(modal.getByText('Name')).toBeVisible();
|
|
await expect(modal.getByText('Color')).toBeVisible();
|
|
await expect(modal.getByText('Permissions')).toBeVisible();
|
|
// Permission groups
|
|
await expect(modal.getByText('Documents')).toBeVisible();
|
|
await expect(modal.getByText('Kanban')).toBeVisible();
|
|
await expect(modal.getByText('Calendar')).toBeVisible();
|
|
await expect(modal.getByText('Members')).toBeVisible();
|
|
});
|
|
|
|
test('should create a custom role and see it in the list', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
const roleName = `Tester ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'Roles' }).click();
|
|
await page.getByRole('button', { name: 'Create Role' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
|
|
await modal.getByPlaceholder('e.g., Moderator').fill(roleName);
|
|
// Select a color (click the second color swatch)
|
|
const colorSwatches = modal.locator('button.rounded-full');
|
|
await colorSwatches.nth(1).click();
|
|
// Create
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Role should appear in the list
|
|
await expect(page.getByText(roleName)).toBeVisible({ timeout: 5000 });
|
|
|
|
// Clean up: delete the role - set up dialog handler BEFORE clicking
|
|
page.on('dialog', dialog => dialog.accept());
|
|
// Find the specific role card that contains the role name and click its Delete button
|
|
const deleteBtn = page.getByRole('button', { name: 'Delete' }).last();
|
|
await deleteBtn.click();
|
|
await expect(page.getByText(roleName)).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|
|
|
|
// ─── Settings: Tags ─────────────────────────────────────────────────────────
|
|
|
|
test.describe('Settings Page - Tags', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/settings`);
|
|
});
|
|
|
|
test('should switch to Tags tab and show tag management UI', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Tags' }).click();
|
|
await expect(page.getByRole('button', { name: 'Create Tag' })).toBeVisible({ timeout: 3000 });
|
|
});
|
|
|
|
test('should create a tag and see it in the list', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
const tagName = `PW Tag ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'Tags' }).click();
|
|
await page.getByRole('button', { name: 'Create Tag' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByLabel('Tag name').fill(tagName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText(tagName)).toBeVisible({ timeout: 5000 });
|
|
|
|
// Clean up: delete the tag
|
|
page.on('dialog', dialog => dialog.accept());
|
|
const tagCard = page.locator('div').filter({ hasText: tagName }).last();
|
|
const deleteBtn = tagCard.locator('button').filter({ hasText: /delete/i }).first();
|
|
if (await deleteBtn.isVisible()) {
|
|
await deleteBtn.click();
|
|
await expect(page.getByText(tagName)).not.toBeVisible({ timeout: 5000 });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── Settings: General ──────────────────────────────────────────────────────
|
|
|
|
test.describe('Settings Page - General', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/settings`);
|
|
});
|
|
|
|
test('should show General tab by default with org info', async ({ page }) => {
|
|
// General tab should be active by default - check for the Settings heading
|
|
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible({ timeout: 3000 });
|
|
// General button should be the active tab
|
|
await expect(page.getByRole('button', { name: 'General' })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// ─── Calendar CRUD ──────────────────────────────────────────────────────────
|
|
|
|
test.describe('Calendar - Event CRUD', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/calendar`);
|
|
});
|
|
|
|
test('should open create event modal via New button', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await expect(modal.getByText('Title')).toBeVisible();
|
|
await expect(modal.getByText('Date')).toBeVisible();
|
|
await expect(modal.getByRole('button', { name: 'Create' })).toBeVisible();
|
|
await expect(modal.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
|
});
|
|
|
|
test('should create an event and see it on the calendar', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
const eventTitle = `PW Event ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
|
|
// Fill in event details
|
|
await modal.getByLabel('Title').fill(eventTitle);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Event should appear on the calendar
|
|
await expect(page.getByText(eventTitle).first()).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should click an event to view details', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
// Create an event first
|
|
const eventTitle = `PW Detail ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByLabel('Title').fill(eventTitle);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Click the event
|
|
await page.getByText(eventTitle).first().click();
|
|
const detailModal = page.getByRole('dialog');
|
|
await expect(detailModal).toBeVisible({ timeout: 3000 });
|
|
await expect(detailModal.getByText(eventTitle)).toBeVisible();
|
|
});
|
|
|
|
test('should delete an event', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
const eventTitle = `PW Delete ${Date.now()}`;
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByLabel('Title').fill(eventTitle);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Switch to Day view so the event is always visible (month view hides overflow behind +N)
|
|
await page.waitForTimeout(500);
|
|
await page.locator('button', { hasText: 'Day' }).filter({ hasText: /^Day$/ }).click();
|
|
await expect(page.getByText(eventTitle).first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click event on calendar to open detail modal, then delete
|
|
await page.getByText(eventTitle).first().click();
|
|
const detailModal = page.getByRole('dialog');
|
|
await expect(detailModal).toBeVisible({ timeout: 5000 });
|
|
|
|
page.on('dialog', dialog => dialog.accept());
|
|
const deleteBtn = detailModal.getByRole('button', { name: 'Delete' });
|
|
if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await deleteBtn.click();
|
|
await expect(detailModal).not.toBeVisible({ timeout: 5000 });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── Kanban Card CRUD ───────────────────────────────────────────────────────
|
|
|
|
test.describe('Kanban - Card CRUD', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/kanban`);
|
|
});
|
|
|
|
test('should create a board and add a card to a column', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
const boardName = `PW Card Board ${Date.now()}`;
|
|
|
|
// Create board
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByPlaceholder('e.g. Sprint 1').fill(boardName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('To Do')).toBeVisible({ timeout: 5000 });
|
|
|
|
// Add a card - click the "Add card" button in the first column
|
|
const addCardBtn = page.getByRole('button', { name: 'Add card' }).first();
|
|
await expect(addCardBtn).toBeVisible({ timeout: 3000 });
|
|
await addCardBtn.click();
|
|
|
|
// Card creation modal should appear
|
|
const cardModal = page.getByRole('dialog');
|
|
await expect(cardModal).toBeVisible({ timeout: 3000 });
|
|
const cardTitle = `PW Card ${Date.now()}`;
|
|
await cardModal.getByLabel('Title').fill(cardTitle);
|
|
await cardModal.getByRole('button', { name: 'Add Card' }).click();
|
|
await expect(cardModal).not.toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText(cardTitle).first()).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should open card detail modal on card click', async ({ page }) => {
|
|
test.setTimeout(60000);
|
|
const boardName = `PW Detail Board ${Date.now()}`;
|
|
|
|
// Create board
|
|
await page.getByRole('button', { name: 'New' }).click();
|
|
const modal = page.getByRole('dialog');
|
|
await expect(modal).toBeVisible({ timeout: 3000 });
|
|
await modal.getByPlaceholder('e.g. Sprint 1').fill(boardName);
|
|
await modal.getByRole('button', { name: 'Create' }).click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('To Do')).toBeVisible({ timeout: 5000 });
|
|
|
|
// Add a card via the "Add card" button
|
|
const addCardBtn = page.getByRole('button', { name: 'Add card' }).first();
|
|
await expect(addCardBtn).toBeVisible({ timeout: 3000 });
|
|
await addCardBtn.click();
|
|
|
|
const cardModal = page.getByRole('dialog');
|
|
await expect(cardModal).toBeVisible({ timeout: 3000 });
|
|
const cardTitle = `PW Click Card ${Date.now()}`;
|
|
await cardModal.getByLabel('Title').fill(cardTitle);
|
|
await cardModal.getByRole('button', { name: 'Add Card' }).click();
|
|
await expect(cardModal).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Click the card to open detail modal
|
|
await page.getByText(cardTitle).first().click();
|
|
const detailModal = page.getByRole('dialog');
|
|
await expect(detailModal).toBeVisible({ timeout: 5000 });
|
|
await expect(detailModal.getByLabel('Title')).toHaveValue(cardTitle);
|
|
});
|
|
});
|
|
|
|
// ─── Settings: Integrations ─────────────────────────────────────────────────
|
|
|
|
test.describe('Settings Page - Integrations', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, `/${TEST_ORG_SLUG}/settings`);
|
|
});
|
|
|
|
test('should show Integrations tab with Google Calendar card', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Integrations' }).click();
|
|
await expect(page.getByRole('heading', { name: 'Google Calendar' })).toBeVisible({ timeout: 3000 });
|
|
// Discord and Slack should show as "Coming soon"
|
|
await expect(page.getByRole('heading', { name: 'Discord' })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Slack' })).toBeVisible();
|
|
await expect(page.getByText('Coming soon').first()).toBeVisible();
|
|
});
|
|
});
|