From b0fa47634ce30a318d96f0ab25583ad7abaf952f Mon Sep 17 00:00:00 2001 From: DarrellRoberts Date: Wed, 20 May 2026 16:32:20 +0200 Subject: [PATCH 1/4] creates test files for randomiser and rating --- e2e/randomiser/randomiser.spec.ts | 0 e2e/rating/rating.spec.ts | 1 + 2 files changed, 1 insertion(+) create mode 100644 e2e/randomiser/randomiser.spec.ts create mode 100644 e2e/rating/rating.spec.ts diff --git a/e2e/randomiser/randomiser.spec.ts b/e2e/randomiser/randomiser.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/e2e/rating/rating.spec.ts b/e2e/rating/rating.spec.ts new file mode 100644 index 0000000..8beb9da --- /dev/null +++ b/e2e/rating/rating.spec.ts @@ -0,0 +1 @@ +import { test, expect } from "@playwright/test" From 7a26ecce44100d563544bd19357d520aed8bfce8 Mon Sep 17 00:00:00 2001 From: DarrellRoberts Date: Wed, 20 May 2026 16:41:44 +0200 Subject: [PATCH 2/4] adds alerts checks to login --- e2e/login/login.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e/login/login.spec.ts b/e2e/login/login.spec.ts index 2738a05..377ee85 100644 --- a/e2e/login/login.spec.ts +++ b/e2e/login/login.spec.ts @@ -44,6 +44,9 @@ test.describe("login flow", async () => { await expect( page.getByRole("heading", { name: "Test" }), ).not.toBeVisible() + await expect( + page.getByRole("alert").filter({ hasText: /login unsuccessful/i }), + ).toBeVisible() }) }) test("unsuccessful login with wrong password", async ({ page }) => { @@ -63,6 +66,9 @@ test.describe("login flow", async () => { await expect( page.getByText(/hmmm incorrect password. let's hope you made a typo./i), ).toBeVisible() + await expect( + page.getByRole("alert").filter({ hasText: /login unsuccessful/i }), + ).toBeVisible() }) }) }) From 7009a6f380d0ad3d995b93d8b0e53812c2975f96 Mon Sep 17 00:00:00 2001 From: DarrellRoberts Date: Thu, 21 May 2026 11:31:33 +0200 Subject: [PATCH 3/4] test: adds rating e2e test --- e2e/rating/rating.spec.ts | 79 +++++++++++++++++++ playwright.config.ts | 24 +++--- .../forms/ratingform/RatingForm.tsx | 1 + 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/e2e/rating/rating.spec.ts b/e2e/rating/rating.spec.ts index 8beb9da..e02bbd6 100644 --- a/e2e/rating/rating.spec.ts +++ b/e2e/rating/rating.spec.ts @@ -1 +1,80 @@ +import { Genre } from "@/types/Genre" import { test, expect } from "@playwright/test" + +test.describe("Happy path to rating a book", () => { + const username = "Test" + const password = "testTEST.123" + let bookId + let token + + test.afterAll("Delete created book", async ({ request }) => { + if (bookId && token) { + await request.delete(`http://localhost:8080/books/${bookId}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + } + }) + + test.beforeEach(async ({ request }) => { + const authResponse = await request.post( + "http://localhost:8080/users/login", + { + data: { username, password }, + }, + ) + + const authData = await authResponse.json() + token = authData.token + + const bookResponse = await request.post("http://localhost:8080/books", { + data: { + title: `testTitle${Math.random() * 100}`, + author: "testAuthor", + pages: 500, + yearPublished: 1992, + genre: [Genre.ACTION], + read: true, + }, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + const bookData = await bookResponse.json() + bookId = bookData._id + }) + + test("submit a 10 rating", async ({ page, context }) => { + await context.addCookies([ + { + name: "token", + value: token, + domain: "localhost", + path: "/", + }, + ]) + await page.goto("http://localhost:3000/") + await test.step("navigate to book page", async () => { + await page.goto(`http://localhost:3000/books/library/${bookId}`) + }) + + await test.step("open rating modal", async () => { + await page + .getByRole("button", { name: "Submit rating", exact: true }) + .click() + }) + + await test.step("enter and submit rating", async () => { + await page.getByTestId("book-rating-input").fill("10") + await page.getByRole("button", { name: "Submit", exact: true }).click() + await expect( + page + .getByRole("alert") + .filter({ hasText: /Rating successfully submitted/i }), + ).toBeVisible() + }) + }) +}) diff --git a/playwright.config.ts b/playwright.config.ts index dfe72ab..69cdc3f 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,6 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test" + +// ◄ FORCE NODE TO LOAD THE ENV VARIABLES FIRST /** * Read environment variables from file. @@ -12,7 +14,7 @@ import { defineConfig, devices } from '@playwright/test'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './e2e', + testDir: "./e2e", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -22,31 +24,31 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: "html", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('')`. */ // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", }, /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, + name: "firefox", + use: { ...devices["Desktop Firefox"] }, }, { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, + name: "webkit", + use: { ...devices["Desktop Safari"] }, }, /* Test against mobile viewports. */ @@ -76,4 +78,4 @@ export default defineConfig({ // url: 'http://localhost:3000', // reuseExistingServer: !process.env.CI, // }, -}); +}) diff --git a/src/components/forms/ratingform/RatingForm.tsx b/src/components/forms/ratingform/RatingForm.tsx index ed282fa..8a36541 100644 --- a/src/components/forms/ratingform/RatingForm.tsx +++ b/src/components/forms/ratingform/RatingForm.tsx @@ -104,6 +104,7 @@ const RatingForm: React.FC = ({ max={10} type="number" step="0.25" + data-testid="book-rating-input" onChange={(e) => { if (Number(e.target.value) > 10 || Number(e.target.value) < 0) { return From 033cd20cbffc2cd53636d8afd410e9f9cbf135b0 Mon Sep 17 00:00:00 2001 From: DarrellRoberts Date: Thu, 21 May 2026 13:45:07 +0200 Subject: [PATCH 4/4] test: adds randomiser e2e test --- e2e/randomiser/randomiser.spec.ts | 71 +++++++++++++++++++ .../CreateUnreadBookForm.tsx | 5 ++ 2 files changed, 76 insertions(+) diff --git a/e2e/randomiser/randomiser.spec.ts b/e2e/randomiser/randomiser.spec.ts index e69de29..d6a68da 100644 --- a/e2e/randomiser/randomiser.spec.ts +++ b/e2e/randomiser/randomiser.spec.ts @@ -0,0 +1,71 @@ +import { Genre } from "@/types/Genre" +import test, { expect } from "@playwright/test" + +test.describe("Happy path to create book selection", async () => { + const username = "Test" + const password = "testTEST.123" + let bookId + let token + + test.afterAll("delete book", async ({ request }) => { + const unreadBooksReq = await request.get( + "http://localhost:8080/books/unread/all", + ) + const unreadBooks = await unreadBooksReq.json() + bookId = unreadBooks?.find((book) => book.title === "e Test Title")?._id + if (bookId && token) { + await request.delete(`http://localhost:8080/books/${bookId}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + } + }) + + test.beforeEach(async ({ request }) => { + const authResponse = await request.post( + "http://localhost:8080/users/login", + { + data: { username, password }, + }, + ) + + const authData = await authResponse.json() + token = authData.token + }) + + test("successfully create a book", async ({ page, context }) => { + await context.addCookies([ + { + name: "token", + value: token, + domain: "localhost", + path: "/", + }, + ]) + await page.goto("http://localhost:3000/") + await test.step("Navigate to book randomiser page", async () => { + await page.goto("http://localhost:3000/books/randomiser") + }) + await test.step("open book from", async () => { + await page.getByRole("button", { name: /add book/i }).click() + }) + await test.step("fill out book form", async () => { + await page.getByTestId("unread-book-title-input").fill("e Test Title") + await page.getByTestId("unread-book-author-input").fill("e Test Author") + await page.getByTestId("unread-book-pages-input").fill("500") + await page.getByTestId("unread-book-yearPublished-input").fill("1992") + await page.getByTestId("unread-book-genre-input").click() + await page.getByText(Genre.ACTION).click() + await page.keyboard.press("Escape") + }) + await test.step("successfully submit book", async () => { + await page.getByRole("button", { name: /submit/i }).click() + + await expect( + page.getByRole("alert").filter({ hasText: /book successfully added/i }), + ).toBeVisible() + }) + }) +}) diff --git a/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx b/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx index da32726..8a6893c 100644 --- a/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx +++ b/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx @@ -113,6 +113,7 @@ const CreateBook: React.FC = () => { handleSubmitSuggestion()} + data-testid="unread-book-title-input" onChange={(e) => dispatch(setFormData({ ...formData, title: e.target.value })) } @@ -147,6 +148,7 @@ const CreateBook: React.FC = () => { > dispatch(setFormData({ ...formData, author: e.target.value })) } @@ -175,6 +177,7 @@ const CreateBook: React.FC = () => { > dispatch( setFormData({ ...formData, pages: Number(e.target.value) }), @@ -206,6 +209,7 @@ const CreateBook: React.FC = () => { > dispatch( setFormData({ @@ -227,6 +231,7 @@ const CreateBook: React.FC = () => { >