-
-
Save gravitylow/c404d461ade92e6cb061877c8a8c45af to your computer and use it in GitHub Desktop.
cursed-playwright-lambda.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Handler } from "aws-lambda"; | |
| import {Client, Guest, Invitation, InvitationFlags, MealChoice, RSVP} from "wedding-client"; | |
| import {chromium as playwright, ConsoleMessage, Page, Request} from "playwright-core"; | |
| import chromium from "@sparticuz/chromium"; | |
| import {expect} from "@playwright/test"; | |
| import * as fs from "node:fs"; | |
| import {PutObjectCommand, S3Client} from "@aws-sdk/client-s3"; | |
| import {Metrics, MetricUnit} from "@aws-lambda-powertools/metrics"; | |
| const s3Client = new S3Client(); | |
| interface TestContext { | |
| testRunId: string, | |
| client: Client, | |
| page: Page, | |
| invitation: Invitation, | |
| firstGuest: Guest, | |
| secondGuest: Guest, | |
| plusOne: Guest, | |
| } | |
| // From https://github.com/mattdsteele/spot-tracker-tracker/blob/main/pizza-function/src/lambda.ts#L7-L46 | |
| const chromeArgs = [ | |
| "--disable-background-networking", | |
| "--disable-background-timer-throttling", | |
| "--disable-backgrounding-occluded-windows", | |
| "--disable-breakpad", | |
| "--disable-client-side-phishing-detection", | |
| "--disable-component-update", | |
| "--disable-default-apps", | |
| "--disable-dev-shm-usage", | |
| "--disable-domain-reliability", | |
| "--disable-extensions", | |
| "--disable-features=AudioServiceOutOfProcess", | |
| "--disable-hang-monitor", | |
| "--disable-ipc-flooding-protection", | |
| "--disable-notifications", | |
| "--disable-offer-store-unmasked-wallet-cards", | |
| "--disable-popup-blocking", | |
| "--disable-print-preview", | |
| "--disable-prompt-on-repost", | |
| "--disable-renderer-backgrounding", | |
| "--disable-setuid-sandbox", | |
| "--disable-speech-api", | |
| "--disable-sync", | |
| "--metrics-recording-only", | |
| "--mute-audio", | |
| "--no-default-browser-check", | |
| "--no-first-run", | |
| "--no-pings", | |
| "--no-sandbox", | |
| "--no-zygote", | |
| "--password-store=basic", | |
| "--single-process", | |
| ]; | |
| export const handler: Handler = async (event: any, context): Promise<any> => { | |
| console.log(event, context); | |
| const tests = [ | |
| testLoginPageUnsuccessful, | |
| testBypassLoginPage, | |
| testLogin, | |
| testNavbarLinks, | |
| testRegistry, | |
| testRsvp, | |
| ]; | |
| const testProps = await setup(); | |
| const startTime = new Date().getTime(); | |
| const metrics = new Metrics({ | |
| namespace: 'wedding_website', | |
| serviceName: 'canary', | |
| }); | |
| console.log('Test run ID: ', testProps.testRunId); | |
| try { | |
| let failedTests = []; | |
| for (const test of tests) { | |
| console.log(`Running test: ${test.name}`); | |
| const result = await runTest(test, testProps); | |
| if (result) { | |
| console.log(`Test passed: ${test.name}`); | |
| } else { | |
| console.log(`Test FAILED: ${test.name}`); | |
| failedTests.push(test); | |
| } | |
| } | |
| if (failedTests.length > 0) { | |
| console.log(`One or more tests FAILED: ${failedTests.map(test => test.name).join(', ')}`); | |
| } else { | |
| console.log('Success!!1!'); | |
| } | |
| const endTime = new Date().getTime(); | |
| await uploadVideos(testProps.testRunId); | |
| metrics.addMetric('failedTestCount', MetricUnit.Count, failedTests.length); | |
| metrics.addMetric('totalTime', MetricUnit.Milliseconds, endTime - startTime); | |
| metrics.addMetric('fault', MetricUnit.Count, 0); | |
| } catch (e) { | |
| metrics.addMetric('fault', MetricUnit.Count, 1); | |
| console.error('Uncaught error in test run', e); | |
| } finally { | |
| metrics.publishStoredMetrics(); | |
| await tearDown(testProps.client, testProps.invitation.invitationId); | |
| } | |
| }; | |
| async function setup(): Promise<any> { | |
| if (!fs.existsSync('/tmp/output')){ | |
| fs.mkdirSync('/tmp/output'); | |
| } | |
| const client = new Client({ endpointPrefix: `${process.env.API_URL}/api/` }); | |
| const testRunId = crypto.randomUUID(); | |
| const createInviteResponse = await client.createInvitation({ | |
| invitationName: testRunId, | |
| lastName: testRunId, | |
| address: '123 Test Ln', | |
| houseNumber: '123', | |
| flags: [InvitationFlags.TestAccount], | |
| guests: [ | |
| { | |
| guestName: 'First Guest', | |
| primaryGuest: true, | |
| }, | |
| { | |
| guestName: 'Second Guest', | |
| primaryGuest: true, | |
| }, | |
| { | |
| guestDescription: 'Plus One', | |
| primaryGuest: false, | |
| } | |
| ] | |
| }); | |
| const invitation = createInviteResponse.invitation!; | |
| const firstGuest = createInviteResponse.guests!.find(guest => guest.guestName == 'First Guest')!; | |
| const secondGuest = createInviteResponse.guests!.find(guest => guest.guestName == 'Second Guest')!; | |
| const plusOne = createInviteResponse.guests!.find(guest => guest.guestDescription == 'Plus One')!; | |
| console.log(`Test setup: invitationId = ${invitation.invitationId}, lastName = ${invitation.lastNameLowercase}, houseNumber = ${invitation.houseNumber}`) | |
| return { | |
| testRunId, | |
| client, | |
| invitation, | |
| firstGuest, | |
| secondGuest, | |
| plusOne, | |
| }; | |
| } | |
| async function tearDown(client: Client, invitationId: string) { | |
| console.log('Tearing down test setup'); | |
| await client.deleteInvitation({ invitationId: invitationId }).then(() => { | |
| console.log('Teardown complete'); | |
| }).catch(err => { | |
| console.error('Error in test teardown', err); | |
| }) | |
| } | |
| async function uploadVideos(testRunId: string) { | |
| const fileNames = fs.readdirSync('/tmp/output'); | |
| for (const file of fileNames) { | |
| console.log('Uploading ', file); | |
| const Key = `${testRunId}/${file}`; | |
| const Body = fs.readFileSync(`/tmp/output/${file}`); | |
| const putObjectCommand = new PutObjectCommand({ | |
| Bucket: process.env.TEST_BUCKET_NAME, | |
| Body, | |
| Key, | |
| }); | |
| const result = await s3Client.send(putObjectCommand); | |
| console.log(result); | |
| } | |
| } | |
| async function runTest(test: Function, testProps: any) { | |
| const metrics = new Metrics({ | |
| namespace: 'wedding_website', | |
| serviceName: 'canary', | |
| }); | |
| metrics.addDimension('testName', test.name); | |
| const setupStartTime = new Date().getTime(); | |
| const browser = await playwright.launch({ | |
| args: [ | |
| ...chromium.args, | |
| ...chromeArgs, | |
| ], | |
| executablePath: await chromium.executablePath(), | |
| }); | |
| const browserContext = await browser.newContext(); | |
| const page = await browserContext.newPage(); | |
| const logs: ConsoleMessage[] = []; | |
| const errors: Error[] = []; | |
| const failedRequests: Request[] = []; | |
| page.on('console', msg => logs.push(msg)); | |
| page.on('pageerror', err => errors.push(err)); | |
| page.on('requestfailed', req => failedRequests.push(req)); | |
| const testStartTime = new Date().getTime(); | |
| try { | |
| await test({ | |
| ...testProps, | |
| page, | |
| }); | |
| metrics.addMetric('testFailed', MetricUnit.Count, 0); | |
| metrics.addMetric('testSuccess', MetricUnit.Count, 1); | |
| return true; | |
| } catch (err) { | |
| console.error(`Error in test: ${test.name}`, err); | |
| console.log('Logs:'); | |
| for (const log of logs) { | |
| console.log(log); | |
| } | |
| console.log('Errors:'); | |
| for (const error of errors) { | |
| console.log(error); | |
| } | |
| console.log('Failed requests:'); | |
| for (const failedReq of failedRequests) { | |
| console.log(failedReq); | |
| } | |
| try { | |
| await page.screenshot({ | |
| path: `/tmp/output/${test.name}.png`, | |
| }); | |
| console.log(`Saved screenshot to /tmp/${test.name}.png`); | |
| } catch (e) { | |
| console.warn("Couldn't save screenshot", e); | |
| } | |
| metrics.addMetric('testFailed', MetricUnit.Count, 1); | |
| metrics.addMetric('testSuccess', MetricUnit.Count, 0); | |
| return false; | |
| } finally { | |
| const testEndTime = new Date().getTime(); | |
| metrics.addMetric('testSetupTime', MetricUnit.Milliseconds, testStartTime - setupStartTime); | |
| metrics.addMetric('testRunTime', MetricUnit.Milliseconds, testEndTime - testStartTime); | |
| metrics.publishStoredMetrics(); | |
| await browser.close(); | |
| } | |
| } | |
| const login = async ({page, invitation}: TestContext) => { | |
| await page.goto(`https://${process.env.WEBSITE_HOSTNAME}/rsvp`); | |
| // Should get forwarded to the login page | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/login?next=%2Frsvp`); | |
| // Fill in required information | |
| await page.getByRole('textbox', { name: 'Your last name' }).fill(invitation.lastNameLowercase); | |
| await page.getByRole('textbox', { name: 'House number on your' }).fill(invitation.houseNumber); | |
| // Enter button is now enabled | |
| const enterButton = page.getByRole('button'); | |
| await expect(enterButton).toBeEnabled(); | |
| // Click enter button | |
| await enterButton.click(); | |
| // Enter button should be disabled while loading | |
| await expect(enterButton).not.toBeEnabled(); | |
| // There should not be an error | |
| await expect(page.locator('.chakra-alert__root')).not.toBeVisible(); | |
| // Should get logged in and forwarded to the RSVP page | |
| await page.waitForURL(`https://${process.env.WEBSITE_HOSTNAME}/rsvp`); | |
| // await page.waitForFunction(() => window.location.href.includes('...')) | |
| } | |
| const testLoginPageUnsuccessful = async ({page}: TestContext) => { | |
| if (process.env.DEMO) { | |
| // Demo website will let any combination of user + house number in | |
| return; | |
| } | |
| await page.goto(`https://${process.env.WEBSITE_HOSTNAME}`); | |
| // Should get forwarded to the login page | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/login`); | |
| await expect(page.getByRole('heading', {name: 'Enter your information to'})).toBeVisible(); | |
| // No error initially | |
| await expect(page.locator('.chakra-alert__root')).not.toBeVisible(); | |
| // Enter button should be disabled initially | |
| const enterButton = page.getByRole('button'); | |
| await expect(enterButton).toBeDisabled(); | |
| // Fill in required information | |
| await page.getByRole('textbox', {name: 'Your last name'}).fill('Test'); | |
| await page.getByRole('textbox', {name: 'House number on your'}).fill('123'); | |
| // Enter button is now enabled | |
| await expect(enterButton).toBeEnabled(); | |
| // Click enter button | |
| await enterButton.click(); | |
| // Enter button should be disabled while loading | |
| await expect(enterButton).not.toBeEnabled(); | |
| // Wait for button to become enabled again | |
| await expect(enterButton).toBeEnabled(); | |
| // There should be an error | |
| await expect(page.locator('.chakra-alert__root')).toBeVisible(); | |
| } | |
| const testBypassLoginPage = async ({page, invitation}: TestContext) => { | |
| await page.goto(`https://${process.env.WEBSITE_HOSTNAME}/rsvp?invitationId=${invitation.invitationId}`); | |
| // Should get logged in and forwarded to the RSVP page | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/rsvp`); | |
| } | |
| const testLogin = async (context: TestContext) => { | |
| await login(context); | |
| } | |
| const testNavbarLinks = async (context: TestContext) => { | |
| await login(context); | |
| const { page } = context; | |
| await page.getByRole('link', { name: 'Our Story' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/our-story`); | |
| await page.getByRole('link', { name: 'Wedding Party', exact: true }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/wedding-party`); | |
| await page.getByRole('link', { name: 'Registry' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/registry`); | |
| await page.getByRole('link', { name: 'RSVP' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/rsvp`); | |
| await page.getByRole('link', { name: 'Location Info' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/location`); | |
| await page.getByRole('link', { name: 'FAQ' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/faq`); | |
| } | |
| const testRegistry = async (context: TestContext) => { | |
| await login(context); | |
| const { page } = context; | |
| await page.goto(`https://${process.env.WEBSITE_HOSTNAME}/registry`); | |
| // Gifts should be visible | |
| await expect(page.getByText('Checked Bags')).toBeVisible(); | |
| await page.getByRole('link', { name: 'Checked Bags' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/registry/item/checked-bags`); | |
| await expect(page.getByRole('spinbutton', { name: 'Quantity' })).toBeVisible(); | |
| await expect(page.getByRole('button', { name: 'increment value' })).toBeVisible(); | |
| await expect(page.getByRole('button', { name: 'Add to Cart' })).toBeVisible(); | |
| await expect(page.getByRole('button', { name: 'View Cart' })).not.toBeVisible(); | |
| await page.getByRole('button', { name: 'increment value' }).click(); | |
| await page.getByRole('button', { name: 'Add to Cart' }).click(); | |
| await expect(page.getByRole('button', { name: 'View Cart' })).toBeVisible(); | |
| await page.getByRole('button', { name: 'View Cart' }).click(); | |
| await expect(page).toHaveURL(`https://${process.env.WEBSITE_HOSTNAME}/registry/cart`); | |
| await expect(page.getByRole('heading', { name: 'Registry Cart (2 items)' })).toBeVisible(); | |
| await expect(page.getByText('Total')).toBeVisible(); | |
| await page.getByLabel('Select quantity').selectOption('3'); | |
| await expect(page.getByRole('heading', { name: 'Registry Cart (3 items)' })).toBeVisible(); | |
| } | |
| const testRsvp = async (context: TestContext) => { | |
| const { page, client, invitation, firstGuest, secondGuest, plusOne } = context; | |
| await login(context); | |
| await page.goto(`https://${process.env.WEBSITE_HOSTNAME}/rsvp`); | |
| // Elements we want to inspect | |
| const firstGuestCard = await page.getByTestId(`guest-card-${firstGuest.guestId}`); | |
| const secondGuestCard = await page.getByTestId(`guest-card-${secondGuest.guestId}`); | |
| const plusOneCard = await page.getByTestId(`guest-card-${plusOne.guestId}`); | |
| const firstGuestHeading = await page.getByRole('heading', { name: 'First Guest' }); | |
| const secondGuestHeading = await page.getByRole('heading', { name: 'Second Guest' }); | |
| const plusOneGuestHeading = await page.getByText('Plus One'); | |
| const rsvpButton = await page.getByRole('button', { name: 'Send RSVP' }); | |
| const updateRsvpButton = await page.getByRole('button', { name: 'Update RSVP' }); | |
| await expect(firstGuestCard).not.toBeVisible(); | |
| await expect(secondGuestCard).not.toBeVisible(); | |
| await expect(plusOneCard).not.toBeVisible(); | |
| await expect(firstGuestHeading).not.toBeVisible(); | |
| await expect(secondGuestHeading).not.toBeVisible(); | |
| await expect(plusOneGuestHeading).not.toBeVisible(); | |
| await expect(rsvpButton).not.toBeVisible(); | |
| await expect(updateRsvpButton).not.toBeVisible(); | |
| // RSVP should be empty | |
| const rsvpInput = page.getByRole('combobox', { name: 'Your RSVP' }); | |
| await expect(rsvpInput).toBeVisible(); | |
| // Set RSVP to yes | |
| await page.getByRole('combobox', { name: 'Your RSVP' }).click(); | |
| await page.getByRole('option', { name: 'Yes! We will attend' }).click(); | |
| // Guest RSVPs and RSVP button should appear | |
| await expect(firstGuestCard).toBeVisible(); | |
| await expect(secondGuestCard).toBeVisible(); | |
| await expect(plusOneCard).toBeVisible(); | |
| await expect(firstGuestHeading).toBeVisible(); | |
| await expect(secondGuestHeading).toBeVisible(); | |
| await expect(plusOneGuestHeading).toBeVisible(); | |
| await expect(rsvpButton).toBeVisible(); | |
| // Give the plus one a name | |
| await page.getByRole('button', { name: 'edit' }).click(); | |
| await page.getByRole('textbox', { name: 'editable input' }).fill('The King of England'); | |
| await page.getByRole('button', { name: 'submit' }).click(); | |
| // Now RSVP for them | |
| await expect(plusOneCard.getByRole('combobox', { name: 'RSVP' })).not.toBeDisabled(); | |
| // Meal choice is disabled until they RSVP Yes | |
| await expect(plusOneCard.getByRole('combobox', { name: 'Meal Choice' })).toBeDisabled(); | |
| await plusOneCard.getByRole('combobox', { name: 'RSVP' }).click(); | |
| await page.getByRole('option', { name: 'Attending' }).click(); | |
| // Attempting to RSVP should fail with a bunch of errors | |
| await rsvpButton.click(); | |
| await expect(page.getByText('Error')).toBeVisible(); | |
| await expect(page.getByText('Please provide a meal choice for The King of England')).toBeVisible(); | |
| await expect(page.getByText('At least one guest must be attending')).toBeVisible(); | |
| // To avoid errors asserting on simultaneous error toasts, wait for 5 seconds | |
| await page.waitForTimeout(5000); | |
| // Add the meal choice for the plus one | |
| await plusOneCard.getByRole('combobox', { name: 'Meal Choice' }).click(); | |
| await page.getByRole('option', { name: 'Whiffletree Farm Chicken' }).click(); | |
| // Now we are only prevented from RSVPing since no primary guest is attending | |
| await rsvpButton.click(); | |
| await expect(page.getByText('Error')).toBeVisible(); | |
| await expect(page.getByText('At least one guest named on the invitation must be attending')).toBeVisible(); | |
| // Primary guests are awaiting RSVP | |
| await expect(firstGuestCard.getByRole('combobox', { name: 'RSVP' })).not.toBeDisabled(); | |
| await expect(firstGuestCard.getByRole('combobox', { name: 'Meal Choice' })).toBeDisabled(); | |
| await expect(secondGuestCard.getByRole('combobox', { name: 'RSVP' })).not.toBeDisabled(); | |
| await expect(secondGuestCard.getByRole('combobox', { name: 'Meal Choice' })).toBeDisabled(); | |
| // RSVP yes, select meal, and add dietary restriction for the first guest | |
| await firstGuestCard.getByRole('combobox', { name: 'RSVP' }).click(); | |
| await page.getByRole('option', { name: 'Attending' }).click(); | |
| await expect(firstGuestCard.getByRole('combobox', { name: 'Meal Choice' })).not.toBeDisabled(); | |
| await firstGuestCard.getByRole('combobox', { name: 'Meal Choice' }).click(); | |
| await page.getByRole('option', { name: 'Organic Seared Salmon' }).click(); | |
| await expect(firstGuestCard.getByRole('button', { name: 'Add Dietary Restriction' })).not.toBeDisabled(); | |
| await expect(firstGuestCard.getByRole('button', { name: 'Add Accommodation Request' })).not.toBeDisabled(); | |
| await firstGuestCard.getByRole('button', { name: 'Add Dietary Restriction' }).click(); | |
| await expect(page.getByTestId(`guest-dietary-restrictions-input-${firstGuest.guestId}`)).toBeVisible(); | |
| await page.getByTestId(`guest-dietary-restrictions-input-${firstGuest.guestId}`).fill('Pesca-Pescatarian'); | |
| // RSVP no for the second guest | |
| await secondGuestCard.getByRole('combobox', { name: 'RSVP' }).click(); | |
| await page.getByRole('option', { name: 'Unable to attend' }).click(); | |
| await expect(secondGuestCard.getByRole('combobox', { name: 'Meal Choice' })).toBeDisabled(); | |
| await expect(secondGuestCard.getByRole('button', { name: 'Add Dietary Restriction' })).toBeDisabled(); | |
| await expect(secondGuestCard.getByRole('button', { name: 'Add Accommodation Request' })).toBeDisabled(); | |
| // Now can RSVP | |
| await rsvpButton.click(); | |
| // Successful RSVP yes | |
| await expect(page.getByText('Success')).toBeVisible(); | |
| await expect(page.getByText('Your RSVP has been updated.')).toBeVisible(); | |
| await expect(page.getByRole('heading', { name: 'You RSVP\'d YES for 2 guests!' })).toBeVisible(); | |
| await expect(page.getByRole('link', { name: 'day-of and travel info' })).toBeVisible() | |
| await expect(page.getByText('Need to change your RSVP?')).toBeVisible(); | |
| await expect(page.getByRole('link', { name: 'wedding registry?' })).toBeVisible(); | |
| // To avoid errors asserting on simultaneous error toasts, wait for 5 seconds | |
| await page.waitForTimeout(5000); | |
| // Verify server-side state | |
| const invitationUpdate1 = await client.getInvitation({ | |
| invitationId: invitation.invitationId | |
| }); | |
| expect(invitationUpdate1.invitation!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate1.invitation!.attendingGuestsCount).toBe(2); | |
| expect(invitationUpdate1.invitation!.lastRsvpTimestamp).toBeDefined(); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == firstGuest.guestId)!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == firstGuest.guestId)!.mealChoice).toBe(MealChoice.Salmon); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == firstGuest.guestId)!.rsvpChangeCount).toBe(1); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == firstGuest.guestId)!.accommodationRequests).toBeUndefined(); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == firstGuest.guestId)!.dietaryRestrictions).toBe('Pesca-Pescatarian'); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == secondGuest.guestId)!.rsvp).toBe(RSVP.No); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == secondGuest.guestId)!.mealChoice).toBeUndefined(); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == secondGuest.guestId)!.rsvpChangeCount).toBe(1); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == secondGuest.guestId)!.accommodationRequests).toBeUndefined(); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == secondGuest.guestId)!.dietaryRestrictions).toBeUndefined(); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == plusOne.guestId)!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == plusOne.guestId)!.guestName).toBe('The King of England'); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == plusOne.guestId)!.mealChoice).toBe(MealChoice.Chicken); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == plusOne.guestId)!.rsvpChangeCount).toBe(1); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == plusOne.guestId)!.accommodationRequests).toBeUndefined(); | |
| expect(invitationUpdate1.guests!.find(guest => guest.guestId == plusOne.guestId)!.dietaryRestrictions).toBeUndefined(); | |
| // Reload the page | |
| await page.goto(`https://${process.env.WEBSITE_HOSTNAME}/rsvp`); | |
| // Result should persist | |
| await expect(page.getByRole('heading', { name: 'You RSVP\'d YES for 2 guests!' })).toBeVisible(); | |
| // Change RSVP | |
| await page.getByText('Need to change your RSVP?').click(); | |
| // Everything should be populated as before | |
| await expect(page.getByRole('combobox', { name: 'Your RSVP' })).toContainText('Yes! We will attend'); | |
| await expect(firstGuestCard.getByRole('combobox', { name: 'RSVP' })).toContainText('Attending'); | |
| await expect(firstGuestCard.getByRole('combobox', { name: 'Meal Choice' })).toContainText('Organic Seared Salmon'); | |
| await expect(page.getByTestId(`guest-dietary-restrictions-input-${firstGuest.guestId}`)).toHaveValue('Pesca-Pescatarian'); | |
| await expect(page.getByTestId(`guest-accommodations-button-${firstGuest.guestId}`)).toBeEnabled(); | |
| await expect(page.getByTestId(`guest-accommodations-input-${firstGuest.guestId}`)).not.toBeVisible(); | |
| await expect(firstGuestCard.getByRole('button', { name: 'Add Dietary Restriction' })).not.toBeVisible(); | |
| await expect(secondGuestCard.getByRole('combobox', { name: 'RSVP' })).toContainText('Unable to attend'); | |
| await expect(secondGuestCard.getByRole('combobox', { name: 'Meal Choice' })).toBeDisabled(); | |
| await expect(page.getByTestId(`guest-dietary-restrictions-input-${secondGuest.guestId}`)).not.toBeVisible(); | |
| await expect(page.getByTestId(`guest-accommodations-input-${secondGuest.guestId}`)).not.toBeVisible(); | |
| await expect(page.getByTestId(`guest-accommodations-button-${secondGuest.guestId}`)).toBeDisabled(); | |
| await expect(secondGuestCard.getByRole('button', { name: 'Add Dietary Restriction' })).toBeDisabled(); | |
| await expect(plusOneCard.getByRole('combobox', { name: 'RSVP' })).toContainText('Attending'); | |
| await expect(plusOneCard.getByRole('combobox', { name: 'Meal Choice' })).toContainText('Whiffletree Farm Chicken'); | |
| await expect(page.getByTestId(`guest-dietary-restrictions-input-${plusOne.guestId}`)).not.toBeVisible(); | |
| await expect(page.getByTestId(`guest-accommodations-input-${plusOne.guestId}`)).not.toBeVisible(); | |
| await expect(page.getByTestId(`guest-accommodations-button-${plusOne.guestId}`)).toBeEnabled(); | |
| await expect(plusOneCard.getByRole('button', { name: 'Add Dietary Restriction' })).toBeEnabled(); | |
| // Change RSVP of second guest | |
| await secondGuestCard.getByRole('combobox', { name: 'RSVP' }).click(); | |
| await page.getByRole('option', { name: 'Attending' }).click(); | |
| await expect(secondGuestCard.getByRole('combobox', { name: 'Meal Choice' })).not.toBeDisabled(); | |
| await secondGuestCard.getByRole('combobox', { name: 'Meal Choice' }).click(); | |
| await page.getByRole('option', { name: 'Chilly Hollow Vegetable Tasting' }).click(); | |
| await expect(secondGuestCard.getByRole('button', { name: 'Add Dietary Restriction' })).not.toBeDisabled(); | |
| await expect(secondGuestCard.getByRole('button', { name: 'Add Accommodation Request' })).not.toBeDisabled(); | |
| // Add an accommodation to the plus one | |
| await page.getByTestId(`guest-accommodations-button-${plusOne.guestId}`).click(); | |
| await expect(page.getByTestId(`guest-accommodations-input-${plusOne.guestId}`)).toBeVisible(); | |
| await page.getByTestId(`guest-accommodations-input-${plusOne.guestId}`).fill('Red carpet please!'); | |
| // Update RSVP | |
| await updateRsvpButton.click(); | |
| // Successful RSVP yes | |
| await expect(page.getByText('Success')).toBeVisible(); | |
| await expect(page.getByText('Your RSVP has been updated.')).toBeVisible(); | |
| await expect(page.getByRole('heading', { name: 'You RSVP\'d YES for 3 guests!' })).toBeVisible(); | |
| await expect(page.getByText('Need to change your RSVP?')).toBeVisible(); | |
| // To avoid errors asserting on simultaneous error toasts, wait for 5 seconds | |
| await page.waitForTimeout(5000); | |
| // Verify server-side state | |
| const invitationUpdate2 = await client.getInvitation({ | |
| invitationId: invitation.invitationId | |
| }); | |
| expect(invitationUpdate2.invitation!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate2.invitation!.attendingGuestsCount).toBe(3); | |
| expect(invitationUpdate2.invitation!.lastRsvpTimestamp).toBeDefined(); | |
| expect(invitationUpdate2.invitation!.lastRsvpTimestamp).toBeGreaterThan(invitationUpdate1.invitation!.lastRsvpTimestamp!); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == firstGuest.guestId)!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == firstGuest.guestId)!.mealChoice).toBe(MealChoice.Salmon); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == firstGuest.guestId)!.rsvpChangeCount).toBe(2); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == firstGuest.guestId)!.accommodationRequests).toBeUndefined(); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == firstGuest.guestId)!.dietaryRestrictions).toBe('Pesca-Pescatarian'); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == secondGuest.guestId)!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == secondGuest.guestId)!.mealChoice).toBe(MealChoice.VegetableTasting); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == secondGuest.guestId)!.rsvpChangeCount).toBe(2); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == secondGuest.guestId)!.accommodationRequests).toBeUndefined(); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == secondGuest.guestId)!.dietaryRestrictions).toBeUndefined(); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == plusOne.guestId)!.rsvp).toBe(RSVP.Yes); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == plusOne.guestId)!.guestName).toBe('The King of England'); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == plusOne.guestId)!.mealChoice).toBe(MealChoice.Chicken); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == plusOne.guestId)!.rsvpChangeCount).toBe(2); | |
| // TODO fix | |
| // expect(invitationUpdate2.guests!.find(guest => guest.guestId == plusOne.guestId).accommodationRequests).toBe('Red carpet please!'); | |
| expect(invitationUpdate2.guests!.find(guest => guest.guestId == plusOne.guestId)!.dietaryRestrictions).toBeUndefined(); | |
| // Change RSVP | |
| await page.getByText('Need to change your RSVP?').click(); | |
| // Set RSVP to no | |
| await page.getByRole('combobox', { name: 'Your RSVP' }).click(); | |
| await page.getByRole('option', { name: 'Regretfully, we are unable to' }).click(); | |
| // Button to add a message should become visible | |
| await expect(page.getByRole('button', { name: 'Here' })).toBeVisible(); | |
| await page.getByRole('button', { name: 'Here' }).click(); | |
| await expect(page.getByRole('textbox', { name: 'Add a message to the Bride' })).toBeVisible(); | |
| await page.getByRole('textbox', { name: 'Add a message to the Bride' }).fill('Maybe next time!'); | |
| // Update RSVP | |
| await updateRsvpButton.click(); | |
| // Successful RSVP no | |
| await expect(page.getByRole('heading', { name: 'You RSVP\'d no' })).toBeVisible(); | |
| await expect(page.getByText('Need to change your RSVP?')).toBeVisible(); | |
| // Verify server-side state | |
| const invitationUpdate3 = await client.getInvitation({ | |
| invitationId: invitation.invitationId | |
| }); | |
| expect(invitationUpdate3.invitation!.rsvp).toBe(RSVP.No); | |
| expect(invitationUpdate3.invitation!.attendingGuestsCount).toBe(0); | |
| expect(invitationUpdate3.invitation!.lastRsvpTimestamp).toBeDefined(); | |
| expect(invitationUpdate3.invitation!.lastRsvpTimestamp).toBeGreaterThan(invitationUpdate2.invitation!.lastRsvpTimestamp!); | |
| expect(invitationUpdate3.guests!.find(guest => guest.guestId == firstGuest.guestId)!.rsvp).toBe(RSVP.No); | |
| expect(invitationUpdate3.guests!.find(guest => guest.guestId == firstGuest.guestId)!.rsvpChangeCount).toBe(3); | |
| expect(invitationUpdate3.guests!.find(guest => guest.guestId == secondGuest.guestId)!.rsvp).toBe(RSVP.No); | |
| expect(invitationUpdate3.guests!.find(guest => guest.guestId == secondGuest.guestId)!.rsvpChangeCount).toBe(3); | |
| expect(invitationUpdate3.guests!.find(guest => guest.guestId == plusOne.guestId)!.rsvp).toBe(RSVP.No); | |
| expect(invitationUpdate3.guests!.find(guest => guest.guestId == plusOne.guestId)!.rsvpChangeCount).toBe(3); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment