Skip to content

Commit

Permalink
Add recreate project E2E test
Browse files Browse the repository at this point in the history
  • Loading branch information
myieye committed Oct 4, 2024
1 parent 0a398bd commit 47d435c
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 4 deletions.
22 changes: 22 additions & 0 deletions frontend/tests/components/deleteProjectModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { type Locator, type Page } from '@playwright/test';
import { BaseComponent } from './baseComponent';

export class DeleteProjectModal extends BaseComponent {

get confirmationInput(): Locator {
return this.componentLocator.getByLabel(`Enter 'DELETE PROJECT' to confirm`);
}

get submitButton(): Locator {
return this.componentLocator.getByRole('button', { name: 'Delete Project' });
}

constructor(page: Page) {
super(page, page.locator(`dialog.modal:has-text("Enter 'DELETE PROJECT' to confirm")`));
}

async confirmDeleteProject(): Promise<void> {
await this.confirmationInput.fill('DELETE PROJECT');
await this.submitButton.click();
}
}
2 changes: 1 addition & 1 deletion frontend/tests/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface TempProject {
name: string
}

type CreateProjectResponse = {data: {createProject: {createProjectResponse: {id: UUID}}}}
export type CreateProjectResponse = {data: {createProject: {createProjectResponse: {id: UUID}}}}

type Fixtures = {
contextFactory: (options: BrowserContextOptions) => Promise<BrowserContext>,
Expand Down
7 changes: 7 additions & 0 deletions frontend/tests/pages/adminDashboardPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Locator, Page } from '@playwright/test';

import { AuthenticatedBasePage } from './authenticatedBasePage';
import { CreateProjectPage } from './createProjectPage';
import { ProjectPage } from './projectPage';

export class AdminDashboardPage extends AuthenticatedBasePage {
Expand All @@ -19,4 +21,9 @@ export class AdminDashboardPage extends AuthenticatedBasePage {
const table = this.page.locator('table').nth(0);
return table.getByRole('link', {name: projectName, exact: true}).click();
}

async clickCreateProject(): Promise<CreateProjectPage> {
await this.page.getByRole('link', {name: 'Create Project', exact: true}).click();
return new CreateProjectPage(this.page).waitFor();
}
}
25 changes: 25 additions & 0 deletions frontend/tests/pages/createProjectPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BasePage } from './basePage';
import type { Page } from '@playwright/test';

export class CreateProjectPage extends BasePage {
constructor(page: Page) {
super(page, page.getByRole('heading', { name: 'Create Project' }), `/project/create`);
}

async fillForm(values: { code: string, customCode?: boolean, name?: string, type?: string, purpose?: string, description?: string }): Promise<void> {
const { code, customCode = false, name = code, type, purpose = 'Software Developer', description = name } = values;
await this.page.getByLabel('Name').fill(name);
await this.page.getByLabel('Description').fill(description ?? name);
if (type) await this.page.getByLabel('Project type').selectOption({ label: type });
if (purpose) await this.page.getByLabel('Purpose').selectOption({ label: purpose });
await this.page.getByLabel('Language Code').fill(code);
if (customCode) {
await this.page.getByLabel('Custom Code').check();
await this.page.getByLabel('Code', { exact: true }).fill(code);
}
}

async submit(): Promise<void> {
await this.page.getByRole('button', {name: 'Create Project'}).click();
}
}
6 changes: 4 additions & 2 deletions frontend/tests/pages/projectPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Locator, Page } from '@playwright/test';

import { AddMemberModal } from '../components/addMemberModal';
import { BasePage } from './basePage';
import { DeleteProjectModal } from '../components/deleteProjectModal';
import { ResetProjectModal } from '../components/resetProjectModal';

export class ProjectPage extends BasePage {
Expand All @@ -12,7 +13,7 @@ export class ProjectPage extends BasePage {
get addMemberButton(): Locator { return this.page.getByRole('button', {name: 'Add/Invite Member'}); }

constructor(page: Page, name: string, code: string) {
super(page, page.locator(`.breadcrumbs :text('${name}')`), `/project/${code}`);
super(page, page.getByRole('heading', {name: `Project: ${name}`}), `/project/${code}`);
}

openMoreSettings(): Promise<void> {
Expand All @@ -24,9 +25,10 @@ export class ProjectPage extends BasePage {
return new AddMemberModal(this.page).waitFor()
}

async clickDeleteProject(): Promise<void> {
async clickDeleteProject(): Promise<DeleteProjectModal> {
await this.openMoreSettings();
await this.deleteProjectButton.click();
return await new DeleteProjectModal(this.page).waitFor();
}

async clickResetProject(): Promise<ResetProjectModal> {
Expand Down
40 changes: 40 additions & 0 deletions frontend/tests/recreateProject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as testEnv from './envVars';

import { AdminDashboardPage } from './pages/adminDashboardPage';
import { ProjectPage } from './pages/projectPage';
import { loginAs } from './utils/authHelpers';
import { test, type CreateProjectResponse } from './fixtures';
import { waitForGqlResponse } from './utils/gqlHelpers';
import { expect } from '@playwright/test';

test('delete and recreate project', async ({ page, uniqueTestId }) => {
// Step 1: Login
await loginAs(page.request, 'admin', testEnv.defaultPassword);
const adminDashboard = await new AdminDashboardPage(page).goto();

// Step 2: Create a new project
const createProjectPage = await adminDashboard.clickCreateProject();
const projectCode = `recreate-project-test-${uniqueTestId}`;
await createProjectPage.fillForm({ code: projectCode, customCode: true });
await createProjectPage.submit();
const projectPage = await new ProjectPage(page, projectCode, projectCode).waitFor();

// Step 3: Delete the project
const deleteProjectModal = await projectPage.clickDeleteProject();
await deleteProjectModal.confirmDeleteProject();
await adminDashboard.waitFor();

// Step 4: Recreate the project
await adminDashboard.clickCreateProject();
await createProjectPage.fillForm({ code: projectCode, customCode: true });

const createProjectResponse = await waitForGqlResponse<CreateProjectResponse>(page, async () => {
await createProjectPage.submit();
});
await projectPage.waitFor();

// Step 5: Clean up the last created project (the first one is only soft deleted, but that's probably fine)
const projectId = createProjectResponse.data.createProject.createProjectResponse.id;
const deleteResponse = await page.request.delete(`${testEnv.serverBaseUrl}/api/project/${projectId}`);
expect(deleteResponse.ok()).toBeTruthy();
});
13 changes: 12 additions & 1 deletion frontend/tests/utils/gqlHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, type APIRequestContext } from '@playwright/test';
import { expect, type APIRequestContext, type Page } from '@playwright/test';
import { serverBaseUrl } from '../envVars';

export function validateGqlErrors(json: {errors: unknown, data: unknown}, expectError = false): void {
Expand All @@ -17,3 +17,14 @@ export async function executeGql<T>(api: APIRequestContext, gql: string, expectE
validateGqlErrors(json as {errors: unknown, data: unknown}, expectError);
return json as T;
}

export async function waitForGqlResponse<T>(page: Page, action: () => Promise<void>): Promise<T> {
const gqlPromise = page.waitForResponse('/api/graphql');
await action();
const response = await gqlPromise;
expect(response.ok(), `code was ${response.status()} (${response.statusText()})`).toBeTruthy();
const json: unknown = await response.json();
expect(json, `for query ${response.request().postData()}`).not.toBeNull();
validateGqlErrors(json as { errors: unknown, data: unknown });
return json as T;
}

0 comments on commit 47d435c

Please sign in to comment.