import { expect } from '@playwright/test'; import type { Page } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../../playwright/config'; // ────────────────────────────────────────────────── // Navigation // ────────────────────────────────────────────────── export async function goToReporting(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/reporting'); } export async function goToReportingDetailed(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/reporting/detailed'); } // ────────────────────────────────────────────────── // Entity creation // ────────────────────────────────────────────────── export async function createProject(page: Page, projectName: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project name').fill(projectName); await Promise.all([ page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(), page.waitForResponse( (response) => response.url().includes('/projects') && response.request().method() === 'POST' && response.status() === 201 ), ]); await expect(page.getByText(projectName)).toBeVisible(); } export async function createBillableProject(page: Page, projectName: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project name').fill(projectName); await page.getByText('Non-Billable').click(); await page.getByText('Default Rate').click(); await Promise.all([ page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(), page.waitForResponse( (response) => response.url().includes('/projects') && response.request().method() === 'POST' && response.status() === 201 ), ]); await expect(page.getByText(projectName)).toBeVisible(); } export async function createClient(page: Page, clientName: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/clients'); await expect(page.getByRole('button', { name: 'Create Client' })).toBeVisible(); await page.getByRole('button', { name: 'Create Client' }).click(); await page.getByPlaceholder('Client Name').fill(clientName); await Promise.all([ page.getByRole('button', { name: 'Create Client' }).click(), page.waitForResponse( (response) => response.url().includes('/clients') && response.request().method() === 'POST' && response.status() === 201 ), ]); await expect(page.getByText(clientName)).toBeVisible(); } export async function createProjectWithClient(page: Page, projectName: string, clientName: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project name').fill(projectName); // Select client in the project create modal await page.getByRole('dialog').getByRole('button', { name: 'No Client' }).click(); await page.getByRole('option', { name: clientName }).click(); await Promise.all([ page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(), page.waitForResponse( (response) => response.url().includes('/projects') && response.request().method() === 'POST' && response.status() === 201 ), ]); await expect(page.getByText(projectName)).toBeVisible(); } export async function createTask(page: Page, projectName: string, taskName: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); await expect(page.getByText(projectName)).toBeVisible(); await page.getByText(projectName).click(); await page.getByRole('button', { name: 'Create Task' }).click(); await page.getByPlaceholder('Task Name').fill(taskName); await Promise.all([ page.getByRole('button', { name: 'Create Task' }).click(), page.waitForResponse( (response) => response.url().includes('/tasks') && response.request().method() === 'POST' && response.status() === 201 ), ]); await expect(page.getByText(taskName)).toBeVisible(); } // ────────────────────────────────────────────────── // Time entry creation // ────────────────────────────────────────────────── export async function createTimeEntryWithProject( page: Page, projectName: string, duration: string ) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); await page.getByRole('button', { name: 'Time entry actions' }).click(); await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); await page .getByRole('dialog') .getByRole('textbox', { name: 'Description' }) .fill(`Entry for ${projectName}`); await page.getByRole('button', { name: 'No Project' }).click(); await page.getByRole('option').filter({ hasText: projectName }).click(); await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); await Promise.all([ page.getByRole('button', { name: 'Create Time Entry' }).click(), page.waitForResponse( (response) => response.url().includes('/time-entries') && response.status() === 201 ), ]); } export async function createTimeEntryWithProjectAndTask( page: Page, projectName: string, taskName: string, duration: string ) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); await page.getByRole('button', { name: 'Time entry actions' }).click(); await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); await page .getByRole('dialog') .getByRole('textbox', { name: 'Description' }) .fill(`Entry for ${projectName} - ${taskName}`); // Open the project/task dropdown await page.getByRole('button', { name: 'No Project' }).click(); // Expand the project's tasks by clicking the "Tasks" button const projectOption = page.getByRole('option').filter({ hasText: projectName }); await projectOption.getByText(/Tasks/).click(); // Select the task (this also selects the project and closes the dropdown) await page.getByText(taskName, { exact: true }).click(); await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); await Promise.all([ page.getByRole('button', { name: 'Create Time Entry' }).click(), page.waitForResponse( (response) => response.url().includes('/time-entries') && response.status() === 201 ), ]); } export async function createTimeEntryWithTag(page: Page, tagName: string, duration: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); await page.getByRole('button', { name: 'Time entry actions' }).click(); await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); await page .getByRole('dialog') .getByRole('textbox', { name: 'Description' }) .fill(`Entry with tag ${tagName}`); // Add tag await page.getByRole('button', { name: 'Tags' }).click(); await page.getByText('Create new tag').click(); await page.getByPlaceholder('Tag Name').fill(tagName); await Promise.all([ page.getByRole('button', { name: 'Create Tag' }).click(), page.waitForResponse( (response) => response.url().includes('/tags') && response.status() === 201 ), ]); await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); await Promise.all([ page.getByRole('button', { name: 'Create Time Entry' }).click(), page.waitForResponse( (response) => response.url().includes('/time-entries') && response.status() === 201 ), ]); } export async function createBareTimeEntry(page: Page, description: string, duration: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); await page.getByRole('button', { name: 'Time entry actions' }).click(); await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); await page.getByRole('dialog').getByRole('textbox', { name: 'Description' }).fill(description); await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); await Promise.all([ page.getByRole('button', { name: 'Create Time Entry' }).click(), page.waitForResponse( (response) => response.url().includes('/time-entries') && response.status() === 201 ), ]); } export async function createTimeEntryWithBillableStatus( page: Page, isBillable: boolean, duration: string ) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); await page.getByRole('button', { name: 'Time entry actions' }).click(); await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); await page .getByRole('dialog') .getByRole('textbox', { name: 'Description' }) .fill(`Time entry ${isBillable ? 'billable' : 'non-billable'}`); if (isBillable) { await page .getByRole('dialog') .getByRole('combobox') .filter({ hasText: 'Non-Billable' }) .click(); await page.getByRole('option', { name: 'Billable', exact: true }).click(); } await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); await Promise.all([ page.getByRole('button', { name: 'Create Time Entry' }).click(), page.waitForResponse( (response) => response.url().includes('/time-entries') && response.status() === 201 ), ]); } // ────────────────────────────────────────────────── // Wait helpers // ────────────────────────────────────────────────── export async function waitForReportingUpdate(page: Page) { await page.waitForResponse( (response) => response.url().includes('/time-entries/aggregate') && response.status() === 200 ); } export async function waitForDetailedReportingUpdate(page: Page) { await page.waitForResponse( (response) => response.url().includes('/time-entries') && !response.url().includes('/aggregate') && response.request().method() === 'GET' && response.status() === 200 ); } // ────────────────────────────────────────────────── // Shared report helpers // ────────────────────────────────────────────────── export async function goToReportingShared(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/reporting/shared'); } export async function saveAsSharedReport( page: Page, reportName: string ): Promise<{ shareableLink: string }> { await page.getByRole('button', { name: 'Save Report' }).click(); await page.getByLabel('Name').fill(reportName); // "Public" checkbox is checked by default const [response] = await Promise.all([ page.waitForResponse( (response) => response.url().includes('/reports') && response.request().method() === 'POST' && response.status() === 201 ), page.getByRole('dialog').getByRole('button', { name: 'Create Report' }).click(), ]); const responseBody = await response.json(); // Wait for navigation to shared reports page await page.waitForURL('**/reporting/shared'); return { shareableLink: responseBody.data.shareable_link }; }