Skip to content

Commit

Permalink
feat: course creation e2e (#386)
Browse files Browse the repository at this point in the history
feat: add e2e for creating course, chapter and text lesson
  • Loading branch information
mateuszszczecina authored Jan 17, 2025
1 parent 529fe60 commit 607ff88
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 3 deletions.
Binary file added apps/web/app/assets/thumbnail-e2e.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion apps/web/app/modules/Admin/AddCourse/AddCourse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ const AddCourse = () => {
</FormControl>
<SelectContent>
{categories.map((category) => (
<SelectItem value={category.id} key={category.id}>
<SelectItem
value={category.id}
key={category.id}
data-testid={`category-option-${category.title}`}
>
{category.title}
</SelectItem>
))}
Expand Down Expand Up @@ -191,6 +195,7 @@ const AddCourse = () => {
/>
{displayThumbnailUrl && (
<Button
name="thumbnail"
onClick={removeThumbnail}
className="mb-4 mt-4 rounded bg-red-500 px-6 py-2 text-white"
>
Expand Down
1 change: 1 addition & 0 deletions apps/web/app/modules/Admin/Courses/Courses.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ const Courses = () => {
{table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-course-id={row.original.id}
data-state={row.getIsSelected() && "selected"}
onClick={() => handleRowClick(row.original.id)}
className="cursor-pointer hover:bg-neutral-100"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const ChapterCard = ({
);

return (
<AccordionItem key={chapter.id} value={chapter.id} className="p-0">
<AccordionItem key={chapter.id} data-chapter-id={chapter.id} value={chapter.id} className="p-0">
<Card
className={cn("mb-4 flex h-full border p-4", {
"border-primary-500": isOpen || selectedChapter?.id === chapter.id,
Expand Down Expand Up @@ -319,7 +319,7 @@ const ChaptersList = ({
}}
className="grid grid-cols-1"
renderItem={(chapter) => (
<SortableList.Item id={chapter.sortableId}>
<SortableList.Item id={chapter.sortableId} data-id={chapter.id}>
<ChapterCard
key={chapter.sortableId}
chapter={chapter}
Expand Down
177 changes: 177 additions & 0 deletions apps/web/e2e/tests/createCourse.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { test, expect } from "@playwright/test";

import { AuthFixture } from "e2e/fixture/auth.fixture";

import type { Locator, Page } from "@playwright/test";

export class CreateCourseActions {
constructor(private readonly page: Page) {}

async openCourse(courseId: string): Promise<void> {
const rowSelector = `[data-course-id="${courseId}"]`;
await this.page.locator(rowSelector).click();
await this.page.waitForURL(`/admin/beta-courses/${courseId}`);
}

async verifyCoursePage(page: Page, courseId: string, expectedTitle: string): Promise<void> {
const currentUrl = await page.url();
expect(currentUrl).toMatch(`/admin/beta-courses/${courseId}`);

const courseTitle = await page.locator("h4").textContent();
expect(courseTitle).toMatch(expectedTitle);
}

async navigateToNewCoursePage(page: Page): Promise<void> {
await page.getByRole("button", { name: /create new/i }).click();
await page.waitForURL("/admin/beta-courses/new");
}

async fillCourseForm(page: Page, expectedTitle: string): Promise<void> {
await page.getByLabel("Title").fill(expectedTitle);

await page.getByLabel("Category").click();
await page.locator('[data-testid="category-option-E2E Testing"]').click();

await page
.getByLabel("Description")
.fill("This course takes you through a css course, it lets you learn the basics.");

const fileInput = await page.locator('input[type="file"]');
const filePath = "app/assets/thumbnail-e2e.jpg";
await fileInput.setInputFiles(filePath);
}

async addChapter(page: Page, chapterTitle: string): Promise<string> {
await page.getByRole("button", { name: /add chapter/i }).click();
await page.getByLabel("Title").fill(chapterTitle);

const createChapterResponsePromise = page.waitForResponse(
(response) => {
return (
response.url().includes("/api/chapter/beta-create-chapter") && response.status() === 201
);
},
{ timeout: 60000 },
);

await page.getByRole("button", { name: /save/i }).click();
const createChapterResponse = await createChapterResponsePromise;
const createChapterResJson = await createChapterResponse.json();
return createChapterResJson.data.id;
}

async addTextLesson(
page: Page,
chapterLocator: Locator,
lessonTitle: string,
lessonDescription: string,
): Promise<void> {
await chapterLocator.getByRole("button", { name: /add lesson/i }).click();

const buttonWithText = await page.locator('h3:has-text("Text")');
const lessonButton = buttonWithText.locator("..");
await lessonButton.click();

await page.getByLabel("Title").fill(lessonTitle);

const descriptionField = page.locator("#description");
const editorInput = descriptionField.locator('div[contenteditable="true"]');
await expect(editorInput).toBeVisible();
await editorInput.click();
await page.keyboard.type(lessonDescription, { delay: 100 });
await expect(editorInput).toHaveText(lessonDescription);
await page.getByRole("button", { name: /save/i }).click();
}
}

test.describe.serial("Course management", () => {
let createCourseActions: CreateCourseActions;
let newCourseId: string;
let newChapterId: string;
const expectedTitle = "CSS Fundamentals";
// Page have to be defined here to use it inside beforeAll, we need it to login as a Admin.
let page: Page;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const authFixture = new AuthFixture(page);
await page.goto("/");
await authFixture.logout();
await page.waitForURL("/auth/login");
await authFixture.login("[email protected]", "password");
});

test.beforeEach(async () => {
await page.goto("/admin/courses");
createCourseActions = new CreateCourseActions(page);
});

test("should click cancel button and back to the course list", async () => {
await createCourseActions.navigateToNewCoursePage(page);

await page.getByRole("button", { name: /cancel/i }).click();
await page.waitForURL("/admin/courses");

const currentUrl = await page.url();
expect(currentUrl).toMatch("/admin/courses");
});

test("should disable the proceed button when form is incomplete", async () => {
await createCourseActions.navigateToNewCoursePage(page);

const proceedButton = page.getByRole("button", { name: /proceed/i });

await expect(proceedButton).toBeDisabled();

await createCourseActions.fillCourseForm(page, expectedTitle);
await expect(proceedButton).not.toBeDisabled();
});

test("should create a new course with chapter and lesson", async () => {
await createCourseActions.navigateToNewCoursePage(page);

await createCourseActions.fillCourseForm(page, expectedTitle);

const proceedButton = page.getByRole("button", { name: /proceed/i });

await expect(proceedButton).not.toBeDisabled();

const courseResponsePromise = page.waitForResponse(
(response) => {
return response.url().includes("/api/course") && response.status() === 201;
},
{ timeout: 60000 },
);

await proceedButton.click();

const courseResponse = await courseResponsePromise;

const courseResJson = await courseResponse.json();
newCourseId = courseResJson.data.id;

await page.waitForURL(`admin/beta-courses/${newCourseId}`);
await createCourseActions.verifyCoursePage(page, newCourseId, expectedTitle);

newChapterId = await createCourseActions.addChapter(page, "CSS Introduction");

const chapterLocator = page.locator(`[data-chapter-id="${newChapterId}"]`);
await expect(chapterLocator).toBeVisible();

await createCourseActions.addTextLesson(
page,
chapterLocator,
"Introduction to CSS",
"CSS is a style sheet language used for describing the presentation of a document written in HTML.",
);

const lessonLocator = chapterLocator.locator('div[aria-label="Lesson: Introduction to CSS"]');
await expect(lessonLocator).toBeVisible();
});

test("should check if course occurs on course list", async () => {
await createCourseActions.openCourse(newCourseId);

await createCourseActions.verifyCoursePage(page, newCourseId, expectedTitle);
});
});

0 comments on commit 607ff88

Please sign in to comment.