diff --git a/api/controller/stripeController.ts b/api/controller/stripeController.ts index 141e0a6..bdbddf6 100644 --- a/api/controller/stripeController.ts +++ b/api/controller/stripeController.ts @@ -93,6 +93,7 @@ export const createCheckout = asyncHandler( res.send({ clientSecret: session.client_secret }); } catch (error) { res.send({ error }).status(404); + return; } } ); diff --git a/api/controller/userController.ts b/api/controller/userController.ts index 035534b..51d1d46 100644 --- a/api/controller/userController.ts +++ b/api/controller/userController.ts @@ -19,15 +19,15 @@ export const updateUserTicketInfo = asyncHandler( return res.status(400).json({ message: "All fields are required" }); } - let updateUserInfoOrNewUser = await insertUserTicket(req.body); + let userTicketId = await insertUserTicket(req.body); res.status(200).json({ - updateUserInfoOrNewUser, + updateUserInfoOrNewUser: userTicketId, }); } catch (error) { + console.log(error); res.status(500).json({ - message: - "Error occured while trying to update/insert a user ticket: " + error, + message: error, }); } } diff --git a/api/gateway/eventsGateway.ts b/api/gateway/eventsGateway.ts index a6184bb..6b049f9 100644 --- a/api/gateway/eventsGateway.ts +++ b/api/gateway/eventsGateway.ts @@ -77,7 +77,11 @@ export async function isTicketAvailableByPriceId( return isTicketAvailable; } -// @Ratchet7x5: Reserve one ticket +/** + * Decrements an event ticket from the tickets (numberTicketsLeft) and events (eventRemainingCapacity) table. + * @param priceId The stripe price ID + * @returns + */ export async function reserveTicket(priceId: string) { let canReserveTicket = await isTicketAvailableByPriceId(priceId); let reservedTicket; @@ -112,7 +116,11 @@ export async function reserveTicket(priceId: string) { return reservedTicket; } -// @Ratchet7x5: Release one reserved ticket +/** + * Increments an event ticket from the tickets (numberTicketsLeft) and events (eventRemainingCapacity) table. + * @param priceId The stripe price ID + * @returns + */ export async function releaseReservedTicket(priceId: string) { let releasedTicket; diff --git a/api/gateway/userGateway.ts b/api/gateway/userGateway.ts index a946af8..6342011 100644 --- a/api/gateway/userGateway.ts +++ b/api/gateway/userGateway.ts @@ -5,13 +5,15 @@ import { peoples, purchasableMemberships, questions, + tickets, + ticketsEventIdLinks, userTickets, userTicketsPeopleIdLinks, userTicketsTicketIdLinks, } from "../schemas/schema"; import { db } from "../db/config/db"; import { User, UpdateUserInfoBody } from "../types/types"; -import { eq } from "drizzle-orm"; +import { eq, and, gte } from "drizzle-orm"; import { stripe } from "../stripe/stripe"; import { getUserEmail, getUserIdByEmail } from "./authGateway"; import { updateUserMetadata } from "supertokens-node/recipe/usermetadata"; @@ -54,6 +56,7 @@ export async function getUserMembershipExpiryDate( return returnDate; } +// refactor the logic: If the current date is past the expiry, they are no longer a member. export async function isMembershipActive(userEmail: string): Promise { let isActive = false; @@ -83,7 +86,7 @@ export async function isMembershipActive(userEmail: string): Promise { /** * Inserts an unpaid ticket into the userTickets (People_Ticket) table - * @param data The payload containing name, email, phoneNumber, answers (q&a). ticketId isn't currently used. + * @param data The payload containing name, email, phoneNumber, answers (q&a). ticketId is tickets.id * @returns */ export async function insertUserTicket(data: { @@ -96,9 +99,60 @@ export async function insertUserTicket(data: { answer: string; }[]; }): Promise<{ userTicketId: number }> { + /** + * Three secnarios can occur in this function: + * 1. Paid ticket: Throw error [DONE] + * 2. Unpaid ticket: return the ticketId (or update info on the existing ticket?) [DONE] + * 3. Uninserted ticket: Insert a new ticket. [PARTIALLY DONE] + * + * A potential refactor of this function may be needed. + * + * Another issue is, a user can purchase different tickets for the same event. + * The caveat is that the second ticket will always be unpaid, but gets inserted into user tickets. + * Since Tickets are linked to the Event, probably use this to prevent the user from buying + */ // return the userTicketId let newUserTicket: { userTicketId: number }[]; + // if the user already has a paid ticket, then we send an error back. + // You will have to use the ticketsEventIdLinks to detect if the ticket belongs to an event. + let prepaidTicket = await db + .select() + .from(userTickets) + .innerJoin( + userTicketsTicketIdLinks, + eq(userTickets.id, userTicketsTicketIdLinks.userTicketId) + ) + .innerJoin( + ticketsEventIdLinks, + eq(userTicketsTicketIdLinks.ticketId, ticketsEventIdLinks.ticketId) + ) + .where( + and( + eq(userTickets.email, data.email), + eq(userTickets.paid, true), + eq(ticketsEventIdLinks.ticketId, data.ticketId) // Ensure ticket ID matches + ) + ); + if (prepaidTicket.length > 0) { + console.log( + "insertUserTicket: prepaidTicket: " + + JSON.stringify(prepaidTicket[0], null, 2) + ); + //if paid, throw error + if (prepaidTicket[0].user_tickets.paid) { + throw new Error( + `You have already purchased a ticket. Contact admin if issue persists.` + ); + } + //NOTE: THIS ELSE IF BLOCK HAS BEEN DISABLED + //if unpaid, return ticketId + /*else if (!prepaidTicket[0].user_tickets.paid) { + return { userTicketId: prepaidTicket[0].user_tickets.id }; + }*/ + } + + //insert a new ticket newUserTicket = await db .insert(userTickets) .values({ @@ -141,11 +195,13 @@ export async function insertUserTicket(data: { throw new Error("insertUserTicket: Unable to insert userTicketIdLink: "); } - console.log("insertUserTicket: userTicketIdLink: " + userTicketIdLink[0]); + console.log( + "insertUserTicket: userTicketIdLink: " + JSON.stringify(userTicketIdLink) + ); const ticketId = newUserTicket[0].userTicketId; - console.dir("insertUserTicket: ticketId: " + ticketId); + console.log("insertUserTicket: ticketId: " + JSON.stringify(ticketId)); if (data.answers.length > 0) { const answerRecords = data.answers.map((answerData) => ({ @@ -160,6 +216,56 @@ export async function insertUserTicket(data: { ); for (let index = 0; index < data.answers.length; index++) { + //TODO: if questions.checkForMemberEmail == true, check for email. If the email is not a member, throw an error. + let question = await db + .select() + .from(questions) + .where(eq(questions.id, answerRecords[index].questionId)); + + console.log( + "insertUserTicket: question: " + JSON.stringify(question, null, 2) + ); + + // if specified email is a member email, then create a new ticket for this user as well + if (question[0].checkForMemberEmail) { + let isMemberEmail = await db + .select() + .from(peoples) + .where( + and( + eq(peoples.email, answerRecords[index].answer), + gte(peoples.memberExpiryDate, new Date().toISOString()) + ) + ) + .catch((error) => { + throw new Error( + `insertUserTicket: isMemberEmail: Error while trying to retrieve ${answerRecords[index].answer} from database: ${error}` + ); + }); + + console.log( + "insertUserTicket: isMemberEmail: " + + JSON.stringify(isMemberEmail, null, 2) + ); + + // member is either not a paid member, or doesn't exist. + if (isMemberEmail.length == 0) { + throw new Error( + `insertUserTicket: ${answerRecords[index].answer} is not a paid member of AUIS. Contact admin if they are. Use ticketId ${ticketId} as reference.` + ); + } else if ( + isMemberEmail.length > 0 && + isMemberEmail[0].memberExpiryDate !== undefined && + isMemberEmail[0].memberExpiryDate !== null + ) { + console.log( + "insertUserTicket: isMember date comparison: " + + new Date().toISOString() >= + isMemberEmail[0].memberExpiryDate + ); + } + } + let answer = await db .insert(answers) .values(answerRecords[index]) diff --git a/api/index.ts b/api/index.ts index 1cdb80b..5372e19 100644 --- a/api/index.ts +++ b/api/index.ts @@ -106,9 +106,4 @@ const port = Number.parseInt(process.env.PORT || "3000"); // Specify the host parameter as '0.0.0.0' to listen on all network interfaces app.listen(port, "0.0.0.0", () => { console.log(`Backend is now listening on port :${port}`); - console.log(`Backend env vars : - DOMAIN_API=${process.env.DOMAIN_API}, - DOMAIN_FRONTEND=${process.env.DOMAIN_FRONTEND}, - STRIPE_WEBHOOK_ENDPOINT=${process.env.STRIPE_WEBHOOK_ENDPOINT}, - GOOGLE_CLIENT_ID=${process.env.GOOGLE_CLIENT_ID},`); }); diff --git a/api/routes/userRoutes.ts b/api/routes/userRoutes.ts index bc40854..fd478bc 100644 --- a/api/routes/userRoutes.ts +++ b/api/routes/userRoutes.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import UserMetadata from "supertokens-node/recipe/usermetadata"; import { getUserMetadata, updateUserInfo, diff --git a/api/scripts/seed.ts b/api/scripts/seed.ts index a558a8d..7d62d91 100644 --- a/api/scripts/seed.ts +++ b/api/scripts/seed.ts @@ -6,6 +6,8 @@ import { purchasableMemberships, tickets, ticketsEventIdLinks, + questions, + questionsTicketIdLinks, } from "../schemas/schema"; const main = async () => { @@ -18,6 +20,8 @@ const main = async () => { await db.delete(tickets); await db.delete(ticketsEventIdLinks); await db.delete(purchasableMemberships); + await db.delete(questions); + await db.delete(questionsTicketIdLinks); // Add data console.log("Seeding database"); @@ -234,7 +238,47 @@ const main = async () => { ]); //Questions + await db.insert(questions).values([ + { + id: 1, + question: "What is your dance partners' email?", + checkForMemberEmail: true, + publishedAt: new Date().toLocaleString(), + }, + { + id: 2, + question: "What is your dance partners' name?", + checkForMemberEmail: false, + publishedAt: new Date().toLocaleString(), + }, + { + id: 3, + question: + "Do you have any food allergies? Examples: Peanuts, Gluten, Milk", + checkForMemberEmail: false, + publishedAt: new Date().toLocaleString(), + }, + ]); + //QuestionTicketIdLink + await db.insert(questionsTicketIdLinks).values([ + // Dance Series: Shawn Thomas + { + id: 1, + questionId: 1, + ticketId: 3, + }, + { + id: 2, + questionId: 2, + ticketId: 3, + }, + { + id: 3, + questionId: 3, + ticketId: 3, + }, + ]); await db.insert(purchasableMemberships).values([ { @@ -260,7 +304,7 @@ const main = async () => { expiry: new Date( new Date().setFullYear(new Date().getFullYear() + 1) ).toLocaleDateString(), - price: "15.00", + price: "16.00", publishedAt: new Date().toISOString(), }, ]); diff --git a/web/.gitignore b/web/.gitignore index 5c75834..4505613 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -26,3 +26,7 @@ coverage/ *.njsproj *.sln *.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/web/package.json b/web/package.json index 6533ae3..fba1290 100644 --- a/web/package.json +++ b/web/package.json @@ -45,6 +45,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@playwright/test": "^1.49.1", "@testing-library/dom": "^10.1.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^16.0.0", diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 0000000..c29ab10 --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: ".\\playwright\\", + /* Run tests in files in parallel */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + //forbidOnly: !!process.env.CI, + /* Retry on CI only */ + //retries: process.env.CI ? 2 : 0, + /* 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", + /* 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://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/web/playwright/backend.spec.ts b/web/playwright/backend.spec.ts new file mode 100644 index 0000000..6a0b8f9 --- /dev/null +++ b/web/playwright/backend.spec.ts @@ -0,0 +1,327 @@ +import { test, expect } from "@playwright/test"; + +// Base file - DELETE LATER + +test("signup1 - membership purchase", async ({ page }) => { + console.log("Navigating to signup page..."); + await page.goto("http://localhost:5173/"); + await page.getByTestId("Sign-up-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN UP" }).click(); + + const errorMessage = await page.locator( + 'div[data-supertokens="inputErrorMessage"]:has-text("This email already exists. Please sign in instead")' + ); + + await page.waitForTimeout(1000); + + console.log("errorMessage: " + (await errorMessage.isVisible())); + + if (await errorMessage.isVisible()) { + console.log("Trying to exit the test case..."); + test.skip( + true, + "User was already signed up. Remove the test user from the database for this case to succeed." + ); + } else { + console.log("Filling in user information..."); + await page.getByPlaceholder("Eg. John Smith").click(); + await page + .getByPlaceholder("Eg. John Smith") + .pressSequentially("John Smith"); + await page.getByPlaceholder("Eg. John Smith").press("Tab"); + await page + .getByPlaceholder("Enter 000000000 if you don't") + .pressSequentially("123456789"); + await page.getByPlaceholder("Enter 000000000 if you don't").press("Tab"); + await page + .getByPlaceholder("Enter 0000000 if you don't") + .pressSequentially("00000000"); + await page.getByPlaceholder("Eg. Software Engineering").click(); + await page + .getByPlaceholder("Eg. Software Engineering") + .pressSequentially("Software Engineering"); + await page.locator('select[name="yearOfStudy"]').selectOption("1"); + await page + .locator('select[name="isDomestic"]') + .selectOption("Domestic Student"); + await page + .locator('select[name="institution"]') + .selectOption("The University of Auckland"); + await page.getByRole("button", { name: "Submit!" }).click(); + + console.log("Selecting membership and proceeding to payment..."); + await page.getByRole("button", { name: "Purchase" }).nth(1).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); + } +}); + +test("signup2 - membership purchase", async ({ page }) => { + console.log("Navigating to signup page..."); + await page.goto("http://localhost:5173/"); + await page.getByTestId("Sign-up-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test2@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN UP" }).click(); + + const errorMessage = await page.locator( + 'div[data-supertokens="inputErrorMessage"]:has-text("This email already exists. Please sign in instead")' + ); + + await page.waitForTimeout(1000); + + console.log("errorMessage: " + (await errorMessage.isVisible())); + + if (await errorMessage.isVisible()) { + console.log("Trying to exit the test case..."); + test.skip( + true, + "User was already signed up. Remove the test user from the database for this case to succeed." + ); + } else { + console.log("Filling in user information..."); + await page.getByPlaceholder("Eg. John Smith").click(); + await page + .getByPlaceholder("Eg. John Smith") + .pressSequentially("Kate Edgar"); + await page.getByPlaceholder("Eg. John Smith").press("Tab"); + await page + .getByPlaceholder("Enter 000000000 if you don't") + .pressSequentially("123456789"); + await page.getByPlaceholder("Enter 000000000 if you don't").press("Tab"); + await page + .getByPlaceholder("Enter 0000000 if you don't") + .pressSequentially("11111111"); + await page.getByPlaceholder("Eg. Software Engineering").click(); + await page + .getByPlaceholder("Eg. Software Engineering") + .pressSequentially("Computer Science"); + await page.locator('select[name="yearOfStudy"]').selectOption("1"); + await page + .locator('select[name="isDomestic"]') + .selectOption("International Student"); + await page + .locator('select[name="institution"]') + .selectOption("Auckland University of Technology"); + await page.getByRole("button", { name: "Submit!" }).click(); + + console.log("Selecting 6month membership and proceeding to payment..."); + await page.getByRole("button", { name: "Purchase" }).nth(0).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); + } +}); + +test("signin", async ({ page }) => { + await page.setDefaultTimeout(15000); + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + + await page.waitForTimeout(2000); +}); + +test("signin and buy single entry members only shawn thomas ticket", async ({ + page, +}) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page.getByRole("img", { name: "Dance Series: Shawn Thomas" }).click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$15\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + await page.getByRole("button", { name: "Continue" }).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); +}); + +test("signin and buy double entry members only shawn thomas ticket", async ({ + page, +}) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + //await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page + .getByRole("heading", { name: "Dance Series: Shawn Thomas" }) + .click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$28\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + //await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + + //dance partner email + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' email\?$/ }) + .getByRole("textbox") + .pressSequentially("test2@example.com"); + //dance partner name + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' name\?$/ }) + .getByRole("textbox") + .pressSequentially("partner 1"); + //do you have food allergies + await page + .locator("div") + .filter({ + hasText: + /^Do you have any food allergies\? Examples: Peanuts, Gluten, Milk$/, + }) + .getByRole("textbox") + .pressSequentially("No allergies."); + + await page.getByRole("button", { name: "Continue" }).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + await page.getByText("Payment Successful").isVisible(); + + console.log("Test completed successfully."); +}); diff --git a/web/playwright/danceBall.spec.ts b/web/playwright/danceBall.spec.ts new file mode 100644 index 0000000..e780838 --- /dev/null +++ b/web/playwright/danceBall.spec.ts @@ -0,0 +1,155 @@ +import { test, expect } from "@playwright/test"; + +test("user1 members only single entry ticket", async ({ page }) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page.getByRole("img", { name: "Dance Ball" }).click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$6\.99Get Tickets$/ }) + .getByRole("button") + .click(); + + await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + await page.getByRole("button", { name: "Continue" }).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); +}); + +// user1 signs in, buys a double entry ticket with another valid member's email added in there +test("user1 members only double entry ticket", async ({ page }) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + //await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page.getByRole("heading", { name: "Dance Ball" }).click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$10\.99Get Tickets$/ }) + .getByRole("button") + .click(); + + //await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + + //dance partner email + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' email\?$/ }) + .getByRole("textbox") + .pressSequentially("test2@example.com"); + //dance partner name + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' name\?$/ }) + .getByRole("textbox") + .pressSequentially("partner 1"); + //do you have food allergies + await page + .locator("div") + .filter({ + hasText: + /^Do you have any food allergies\? Examples: Peanuts, Gluten, Milk$/, + }) + .getByRole("textbox") + .pressSequentially("No allergies."); + + await page.getByRole("button", { name: "Continue" }).click(); + + const response = await page.waitForResponse( + (response) => + response.url().includes("api/user/user-ticket-info") && + response.status() === 500 + ); + const responseBody = await response.json(); + console.log("Specific response detected!", responseBody); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + await page.getByText("Payment Successful").isVisible(); + + console.log("Test completed successfully."); +}); diff --git a/web/playwright/exhaustTickets.spec.ts b/web/playwright/exhaustTickets.spec.ts new file mode 100644 index 0000000..c5b8955 --- /dev/null +++ b/web/playwright/exhaustTickets.spec.ts @@ -0,0 +1,329 @@ +import { test, expect } from "@playwright/test"; + +// WIP, not fully complete. +// merge the signup and purchase ticket cases into one +// use a random email generator to exhaust all tickets for an event + +test("signup1 - membership purchase", async ({ page }) => { + console.log("Navigating to signup page..."); + await page.goto("http://localhost:5173/"); + await page.getByTestId("Sign-up-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN UP" }).click(); + + const errorMessage = await page.locator( + 'div[data-supertokens="inputErrorMessage"]:has-text("This email already exists. Please sign in instead")' + ); + + await page.waitForTimeout(1000); + + console.log("errorMessage: " + (await errorMessage.isVisible())); + + if (await errorMessage.isVisible()) { + console.log("Trying to exit the test case..."); + test.skip( + true, + "User was already signed up. Remove the test user from the database for this case to succeed." + ); + } else { + console.log("Filling in user information..."); + await page.getByPlaceholder("Eg. John Smith").click(); + await page + .getByPlaceholder("Eg. John Smith") + .pressSequentially("John Smith"); + await page.getByPlaceholder("Eg. John Smith").press("Tab"); + await page + .getByPlaceholder("Enter 000000000 if you don't") + .pressSequentially("123456789"); + await page.getByPlaceholder("Enter 000000000 if you don't").press("Tab"); + await page + .getByPlaceholder("Enter 0000000 if you don't") + .pressSequentially("00000000"); + await page.getByPlaceholder("Eg. Software Engineering").click(); + await page + .getByPlaceholder("Eg. Software Engineering") + .pressSequentially("Software Engineering"); + await page.locator('select[name="yearOfStudy"]').selectOption("1"); + await page + .locator('select[name="isDomestic"]') + .selectOption("Domestic Student"); + await page + .locator('select[name="institution"]') + .selectOption("The University of Auckland"); + await page.getByRole("button", { name: "Submit!" }).click(); + + console.log("Selecting membership and proceeding to payment..."); + await page.getByRole("button", { name: "Purchase" }).nth(1).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); + } +}); + +test("signup2 - membership purchase", async ({ page }) => { + console.log("Navigating to signup page..."); + await page.goto("http://localhost:5173/"); + await page.getByTestId("Sign-up-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test2@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN UP" }).click(); + + const errorMessage = await page.locator( + 'div[data-supertokens="inputErrorMessage"]:has-text("This email already exists. Please sign in instead")' + ); + + await page.waitForTimeout(1000); + + console.log("errorMessage: " + (await errorMessage.isVisible())); + + if (await errorMessage.isVisible()) { + console.log("Trying to exit the test case..."); + test.skip( + true, + "User was already signed up. Remove the test user from the database for this case to succeed." + ); + } else { + console.log("Filling in user information..."); + await page.getByPlaceholder("Eg. John Smith").click(); + await page + .getByPlaceholder("Eg. John Smith") + .pressSequentially("Kate Edgar"); + await page.getByPlaceholder("Eg. John Smith").press("Tab"); + await page + .getByPlaceholder("Enter 000000000 if you don't") + .pressSequentially("123456789"); + await page.getByPlaceholder("Enter 000000000 if you don't").press("Tab"); + await page + .getByPlaceholder("Enter 0000000 if you don't") + .pressSequentially("11111111"); + await page.getByPlaceholder("Eg. Software Engineering").click(); + await page + .getByPlaceholder("Eg. Software Engineering") + .pressSequentially("Computer Science"); + await page.locator('select[name="yearOfStudy"]').selectOption("1"); + await page + .locator('select[name="isDomestic"]') + .selectOption("International Student"); + await page + .locator('select[name="institution"]') + .selectOption("Auckland University of Technology"); + await page.getByRole("button", { name: "Submit!" }).click(); + + console.log("Selecting 6month membership and proceeding to payment..."); + await page.getByRole("button", { name: "Purchase" }).nth(0).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); + } +}); + +test("signin", async ({ page }) => { + await page.setDefaultTimeout(15000); + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + + await page.waitForTimeout(2000); +}); + +test("signin and buy single entry members only shawn thomas ticket", async ({ + page, +}) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page.getByRole("img", { name: "Dance Series: Shawn Thomas" }).click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$15\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + await page.getByRole("button", { name: "Continue" }).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); +}); + +test("signin and buy double entry members only shawn thomas ticket", async ({ + page, +}) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + //await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page + .getByRole("heading", { name: "Dance Series: Shawn Thomas" }) + .click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$28\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + //await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + + //dance partner email + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' email\?$/ }) + .getByRole("textbox") + .pressSequentially("test2@example.com"); + //dance partner name + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' name\?$/ }) + .getByRole("textbox") + .pressSequentially("partner 1"); + //do you have food allergies + await page + .locator("div") + .filter({ + hasText: + /^Do you have any food allergies\? Examples: Peanuts, Gluten, Milk$/, + }) + .getByRole("textbox") + .pressSequentially("No allergies."); + + await page.getByRole("button", { name: "Continue" }).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + await page.getByText("Payment Successful").isVisible(); + + console.log("Test completed successfully."); +}); diff --git a/web/playwright/shawnThomas.spec.ts b/web/playwright/shawnThomas.spec.ts new file mode 100644 index 0000000..d92415a --- /dev/null +++ b/web/playwright/shawnThomas.spec.ts @@ -0,0 +1,241 @@ +import { test, expect } from "@playwright/test"; + +test("user1 members only single entry ticket", async ({ page }) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page.getByRole("img", { name: "Dance Series: Shawn Thomas" }).click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$15\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + await page.getByRole("button", { name: "Continue" }).click(); + + const errorMessageLocator = await page.locator("text=An error").isVisible(); + console.log( + "Is error message visible in checkout info form: ", + errorMessageLocator + ); + + test.fail( + errorMessageLocator === true, + "A paid ticket for this user and event ticket exists already. Delete it from the database for case to succeed. " + ); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); +}); + +// user1 signs in, buys a double entry ticket with another valid member's email added in there +test("user1 members only double entry ticket", async ({ page }) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + //await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page + .getByRole("heading", { name: "Dance Series: Shawn Thomas" }) + .click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$28\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + //await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + + //dance partner email + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' email\?$/ }) + .getByRole("textbox") + .pressSequentially("test2@example.com"); + //dance partner name + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' name\?$/ }) + .getByRole("textbox") + .pressSequentially("partner 1"); + //do you have food allergies + await page + .locator("div") + .filter({ + hasText: + /^Do you have any food allergies\? Examples: Peanuts, Gluten, Milk$/, + }) + .getByRole("textbox") + .pressSequentially("No allergies."); + + await page.getByRole("button", { name: "Continue" }).click(); + + //const response = await page.waitForResponse( + // (response) => + // response.url().includes("api/user/user-ticket-info") && + // response.status() === 500 + //); + //const responseBody = await response.json(); + //console.log("Specific response detected!", responseBody); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + await page.getByText("Payment Successful").isVisible(); + + console.log("Test completed successfully."); +}); + +// user1 signs in, buys a double entry ticket with an invalid member's email +test("user1 double entry ticket invalid member email", async ({ page }) => { + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + //await page.waitForTimeout(250); + + console.log("navigating to events page..."); + await page.getByRole("link", { name: "Events" }).click(); + await page.waitForTimeout(250); + await page + .getByRole("heading", { name: "Dance Series: Shawn Thomas" }) + .click(); + await page + .locator("div") + .filter({ hasText: /^Get Tickets$/ }) + .getByRole("button") + .click(); + await page + .locator("div") + .filter({ hasText: /^\$28\.00Get Tickets$/ }) + .getByRole("button") + .click(); + + //await page.waitForTimeout(250); + + console.log("Filling in checkout information form..."); + await page.getByRole("textbox").first().click(); + await page.getByRole("textbox").first().pressSequentially("John Smith"); + await page.getByRole("textbox").nth(1).click(); + await page.getByRole("textbox").nth(1).pressSequentially("test@example.com"); + await page.getByRole("textbox").nth(1).press("Tab"); + await page.getByRole("textbox").nth(2).pressSequentially("12345678901"); + + //dance partner email + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' email\?$/ }) + .getByRole("textbox") + .pressSequentially("test4@example.com"); + //dance partner name + await page + .locator("div") + .filter({ hasText: /^What is your dance partners' name\?$/ }) + .getByRole("textbox") + .pressSequentially("partner 1"); + //do you have food allergies + await page + .locator("div") + .filter({ + hasText: + /^Do you have any food allergies\? Examples: Peanuts, Gluten, Milk$/, + }) + .getByRole("textbox") + .pressSequentially("No allergies."); + + await page.getByRole("button", { name: "Continue" }).click(); + + const response = await page.waitForResponse( + (response) => + response.url().includes("api/user/user-ticket-info") && + response.status() === 500 + ); + const responseBody = await response.json(); + console.log("Specific response detected!", responseBody); +}); diff --git a/web/playwright/signin.spec.ts b/web/playwright/signin.spec.ts new file mode 100644 index 0000000..9be4886 --- /dev/null +++ b/web/playwright/signin.spec.ts @@ -0,0 +1,31 @@ +import { test } from "@playwright/test"; + +test("user1 signin", async ({ page }) => { + await page.setDefaultTimeout(15000); + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + + await page.waitForTimeout(2000); +}); + +test("user2 signin", async ({ page }) => { + await page.setDefaultTimeout(15000); + await page.goto("http://localhost:5173/"); + await page.getByTestId("sign-in-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test2@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN IN" }).click(); + + await page.waitForTimeout(2000); +}); diff --git a/web/playwright/signup.spec.ts b/web/playwright/signup.spec.ts new file mode 100644 index 0000000..5e7a17e --- /dev/null +++ b/web/playwright/signup.spec.ts @@ -0,0 +1,159 @@ +import { test, expect } from "@playwright/test"; + +test("signup1 - membership purchase", async ({ page }) => { + console.log("Navigating to signup page..."); + await page.goto("http://localhost:5173/"); + await page.getByTestId("Sign-up-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN UP" }).click(); + + const errorMessage = await page.locator( + 'div[data-supertokens="inputErrorMessage"]:has-text("This email already exists. Please sign in instead")' + ); + + await page.waitForTimeout(1000); + + console.log("errorMessage: " + (await errorMessage.isVisible())); + + if (await errorMessage.isVisible()) { + console.log("Trying to exit the test case..."); + test.skip( + true, + "User was already signed up. Remove the test user from the database for this case to succeed." + ); + } else { + console.log("Filling in user information..."); + await page.getByPlaceholder("Eg. John Smith").click(); + await page + .getByPlaceholder("Eg. John Smith") + .pressSequentially("John Smith"); + await page.getByPlaceholder("Eg. John Smith").press("Tab"); + await page + .getByPlaceholder("Enter 000000000 if you don't") + .pressSequentially("123456789"); + await page.getByPlaceholder("Enter 000000000 if you don't").press("Tab"); + await page + .getByPlaceholder("Enter 0000000 if you don't") + .pressSequentially("00000000"); + await page.getByPlaceholder("Eg. Software Engineering").click(); + await page + .getByPlaceholder("Eg. Software Engineering") + .pressSequentially("Software Engineering"); + await page.locator('select[name="yearOfStudy"]').selectOption("1"); + await page + .locator('select[name="isDomestic"]') + .selectOption("Domestic Student"); + await page + .locator('select[name="institution"]') + .selectOption("The University of Auckland"); + await page.getByRole("button", { name: "Submit!" }).click(); + + console.log("Selecting membership and proceeding to payment..."); + await page.getByRole("button", { name: "Purchase" }).nth(1).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); + } +}); + +test("signup2 - membership purchase", async ({ page }) => { + console.log("Navigating to signup page..."); + await page.goto("http://localhost:5173/"); + await page.getByTestId("Sign-up-mobile").click(); + await page.getByPlaceholder("Email address").click(); + await page + .getByPlaceholder("Email address") + .pressSequentially("test2@example.com"); + await page.getByPlaceholder("Password").click(); + await page.getByPlaceholder("Password").pressSequentially("Password123"); + await page.getByRole("button", { name: "SIGN UP" }).click(); + + const errorMessage = await page.locator( + 'div[data-supertokens="inputErrorMessage"]:has-text("This email already exists. Please sign in instead")' + ); + + await page.waitForTimeout(1000); + + console.log("errorMessage: " + (await errorMessage.isVisible())); + + if (await errorMessage.isVisible()) { + console.log("Trying to exit the test case..."); + test.skip( + true, + "User was already signed up. Remove the test user from the database for this case to succeed." + ); + } else { + console.log("Filling in user information..."); + await page.getByPlaceholder("Eg. John Smith").click(); + await page + .getByPlaceholder("Eg. John Smith") + .pressSequentially("Kate Edgar"); + await page.getByPlaceholder("Eg. John Smith").press("Tab"); + await page + .getByPlaceholder("Enter 000000000 if you don't") + .pressSequentially("123456789"); + await page.getByPlaceholder("Enter 000000000 if you don't").press("Tab"); + await page + .getByPlaceholder("Enter 0000000 if you don't") + .pressSequentially("11111111"); + await page.getByPlaceholder("Eg. Software Engineering").click(); + await page + .getByPlaceholder("Eg. Software Engineering") + .pressSequentially("Computer Science"); + await page.locator('select[name="yearOfStudy"]').selectOption("1"); + await page + .locator('select[name="isDomestic"]') + .selectOption("International Student"); + await page + .locator('select[name="institution"]') + .selectOption("Auckland University of Technology"); + await page.getByRole("button", { name: "Submit!" }).click(); + + console.log("Selecting 6month membership and proceeding to payment..."); + await page.getByRole("button", { name: "Purchase" }).nth(0).click(); + + console.log("Filling in Stripe payment form..."); + const paymentFrame = await page + .locator('iframe[name="embedded-checkout"]') + .contentFrame(); + await paymentFrame + .getByPlaceholder("1234 1234 1234") + .pressSequentially("4242424242424242"); + await paymentFrame.getByPlaceholder("MM / YY").pressSequentially("1242"); + await paymentFrame.getByPlaceholder("CVC").pressSequentially("999"); + await paymentFrame + .getByPlaceholder("Full name on card") + .pressSequentially("test"); + await paymentFrame.getByTestId("hosted-payment-submit-button").click(); + + console.log("Verifying post-payment URL..."); + await expect(page).toHaveURL( + /http:\/\/localhost:5173\/return\?session_id=cs_test_.+/ + ); + + console.log("Test completed successfully."); + } +}); diff --git a/web/yarn.lock b/web/yarn.lock index 9d9f75a..25d879c 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -637,6 +637,13 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@playwright/test@^1.49.1": + version "1.49.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.1.tgz#55fa360658b3187bfb6371e2f8a64f50ef80c827" + integrity sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g== + dependencies: + playwright "1.49.1" + "@remix-run/router@1.15.0": version "1.15.0" resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz" @@ -2181,6 +2188,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" @@ -3549,6 +3561,20 @@ pkg-types@^1.0.3, pkg-types@^1.1.1: mlly "^1.7.0" pathe "^1.1.2" +playwright-core@1.49.1: + version "1.49.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.49.1.tgz#32c62f046e950f586ff9e35ed490a424f2248015" + integrity sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg== + +playwright@1.49.1: + version "1.49.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.49.1.tgz#830266dbca3008022afa7b4783565db9944ded7c" + integrity sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA== + dependencies: + playwright-core "1.49.1" + optionalDependencies: + fsevents "2.3.2" + postcss-import@^15.1.0: version "15.1.0" resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz" @@ -4070,16 +4096,7 @@ std-env@^3.5.0: resolved "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4105,14 +4122,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==