-
Notifications
You must be signed in to change notification settings - Fork 9
Write some initial e2e tests #429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fd9d6b7
c52c38b
d562517
59de3d7
599ca17
4e39768
1635c40
37c8a24
742fe63
b05ebda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,7 +123,9 @@ defmodule BikeBrigade.MixProject do | |
| "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], | ||
| "ecto.reset": ["ecto.drop", "ecto.setup"], | ||
| test: ["ecto.create --quiet", "ecto.migrate", "test"], | ||
| "assets.deploy": ["esbuild default --minify", "tailwind default --minify", "phx.digest"] | ||
| "assets.deploy": ["esbuild default --minify", "tailwind default --minify", "phx.digest"], | ||
| # REVIEW: Sometimes this hangs on a query - `INSERT INTO "tasks_items"...` | ||
| "test.e2e": ["ecto.drop", "phx.server"] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what's going on here—but it just seems to hang. Maybe just remove it? |
||
| ] | ||
| end | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "status": "failed", | ||
| "failedTests": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
|
|
||
| # Playwright | ||
| node_modules/ | ||
| /test-results/ | ||
| /playwright-report/ | ||
| /blob-report/ | ||
| /playwright/.cache/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| import { Page } from '@playwright/test'; | ||
| import { test, expect } from "./helpers/sandbox"; | ||
| import { faker } from '@faker-js/faker'; | ||
|
|
||
| const programName = faker.company.name() | ||
|
|
||
| test.describe('Login and Logout', () => { | ||
| test('Can Login', async ({ page }) => { | ||
| await doLogin(page) | ||
| await expect(page.locator('#flash')).toContainText('Success! Welcome!'); | ||
| }) | ||
| test('Validates phone number', async ({ page }) => { | ||
| await page.goto('http://localhost:4000/login'); | ||
| await page.getByRole('textbox', { name: 'Phone Number' }).click(); | ||
| await page.getByRole('textbox', { name: 'Phone Number' }).fill('647555'); | ||
| await page.getByRole('button', { name: 'Get Login Code' }).click(); | ||
| await expect(page.locator('#login-form')).toContainText('phone number is not valid for Canada'); | ||
| }); | ||
|
|
||
| test('Cancel button returns to login page', async ({ page }) => { | ||
| await page.goto('http://localhost:4000/login'); | ||
| await page.getByRole('textbox', { name: 'Phone Number' }).click(); | ||
| await page.getByRole('textbox', { name: 'Phone Number' }).fill('6475555555'); | ||
| await page.getByRole('button', { name: 'Get Login Code' }).click(); | ||
| await page.getByRole('link', { name: 'Cancel' }).click(); | ||
| await expect(page.getByRole('button')).toContainText('Get Login Code'); | ||
| }); | ||
|
|
||
| test('Clicking sign up takes you to marketing site', async ({ page }) => { | ||
| await page.goto('http://localhost:4000/login'); | ||
| await page.getByRole('link', { name: 'Sign Up!' }).click(); | ||
| await expect(page.locator('h2')).toContainText('Join the Brigade.'); | ||
| }); | ||
| test('Can Logout', async ({ page }) => { | ||
| await doLogin(page) | ||
| await page.getByRole('link', { name: 'Log out' }).click(); | ||
| await expect(page.locator('#flash')).toContainText('Success! Goodbye'); | ||
| }); | ||
| }) | ||
|
|
||
| test.describe('Programs', () => { | ||
|
|
||
|
|
||
| // NOTE: something about this is sometimes fails on a fresh db. | ||
| test('Can create and edit program', async ({ page }) => { | ||
| await doLogin(page) // only needs to run once per describe block | ||
| await page.goto('http://localhost:4000/programs'); | ||
|
|
||
| await createProgram(page, programName) | ||
| await expect(page.getByRole('link', { name: programName, exact: true })).toBeVisible(); | ||
|
|
||
| // editing of program | ||
|
|
||
| await page.getByRole('link', { name: `Edit , ${programName}` }).click(); | ||
| await page.getByRole('textbox', { name: 'Campaign Blurb (please keep' }).click(); | ||
| await page.getByRole('textbox', { name: 'Campaign Blurb (please keep' }).fill('This is a test program that was updated'); | ||
| await page.getByRole('button', { name: 'Save' }).click(); | ||
| await expect(page.getByText('Success! program updated')).toBeVisible(); | ||
|
|
||
|
|
||
| await page.getByRole('link', { name: `Edit , ${programName}` }).click(); | ||
| await page.getByRole('textbox', { name: 'Campaign Blurb (please keep' }).click(); | ||
| await expect(page.getByLabel('Campaign Blurb (please keep')).toContainText('This is a test program that was updated'); | ||
|
|
||
| }) | ||
| }) | ||
|
|
||
| test.describe('Campaigns', () => { | ||
| let page: Page | ||
| let programName: string | ||
|
|
||
| test.beforeAll(async ({ browser }) => { | ||
| programName = faker.company.name() | ||
| page = await browser.newPage() | ||
| await doLogin(page) | ||
| await createProgram(page, programName) | ||
| }) | ||
|
|
||
|
|
||
| test('Can create a campaign', async () => { | ||
| await createCampaign({page, programName, numDays: 0}) | ||
| await expect(page.locator('#flash')).toContainText('Success! Campaign created successfully'); | ||
| await expect(page.getByText(programName)).toBeVisible(); | ||
| }) | ||
|
|
||
| test('Can create a campaign for next week', async () => { | ||
| await createCampaign({page, programName, numDays: 8}) | ||
|
|
||
| // now go check that campaign shows up next week. | ||
| await page.getByRole('link', { name: 'Campaigns' }).click(); | ||
| // goto next week | ||
| await page.getByRole('navigation', { name: 'Pagination' }).getByRole('link').nth(2).click(); | ||
| // ensure that new campaign for next-week is present | ||
| await expect(page.getByText(programName)).toBeVisible(); | ||
| }) | ||
| }) | ||
|
|
||
| async function doLogin(page: any) { | ||
| await page.goto('http://localhost:4000/login'); | ||
| await page.getByRole('textbox', { name: 'Phone Number' }).click(); | ||
| await page.getByRole('textbox', { name: 'Phone Number' }).fill('6475555555'); | ||
| await page.getByRole('button', { name: 'Get Login Code' }).click(); | ||
| await page.getByRole('textbox', { name: 'Authentication Code' }).click(); | ||
| await page.getByRole('textbox', { name: 'Authentication Code' }).fill('123456'); | ||
| await page.getByRole('button', { name: 'Sign in' }).click(); | ||
| } | ||
|
|
||
|
|
||
| function getDatePlusDays(daysToAdd: number) { | ||
| const today = new Date(); | ||
| const futureDate = new Date(today); | ||
| futureDate.setDate(today.getDate() + daysToAdd); | ||
|
|
||
| const year = futureDate.getFullYear(); | ||
| const month = String(futureDate.getMonth() + 1).padStart(2, '0'); | ||
| const day = String(futureDate.getDate()).padStart(2, '0'); | ||
|
|
||
| return `${year}-${month}-${day}`; | ||
| } | ||
|
|
||
| async function createProgram(page: Page, programName: string) { | ||
| await page.goto('http://localhost:4000/programs'); | ||
| await page.getByRole('link', { name: 'Programs' }).click(); | ||
| await page.getByRole('link', { name: 'New Program' }).click(); | ||
| await page.getByRole('textbox', { name: 'Name', exact: true }).click(); | ||
| await page.getByRole('textbox', { name: 'Name', exact: true }).fill(programName); | ||
| await page.getByRole('textbox', { name: 'Campaign Blurb (please keep' }).click(); | ||
| await page.getByRole('textbox', { name: 'Campaign Blurb (please keep' }).fill('This is a test program'); | ||
| await page.getByRole('textbox', { name: 'About (internal description)' }).click(); | ||
| await page.getByRole('textbox', { name: 'About (internal description)' }).fill('This is an internal description'); | ||
| await page.getByRole('textbox', { name: 'Start Date' }).fill('2025-02-12'); | ||
| await page.getByRole('checkbox', { name: 'Public' }).check(); | ||
| await page.getByRole('checkbox', { name: 'Hide Pickup Address' }).check(); | ||
| await page.getByRole('button', { name: 'Add Schedule' }).click(); | ||
|
|
||
| await page.getByRole('textbox', { name: 'Photo Description' }).click(); | ||
| await page.getByRole('textbox', { name: 'Photo Description' }).fill('1 Large Box'); | ||
| await page.getByRole('textbox', { name: 'Contact Name' }).click(); | ||
| await page.getByRole('textbox', { name: 'Contact Name' }).fill('Joe Cool'); | ||
| await page.getByRole('textbox', { name: 'Contact Name' }).press('Tab'); | ||
| await page.getByRole('textbox', { name: 'Contact Email' }).fill('joecool@gmail.com'); | ||
| await page.getByRole('textbox', { name: 'Contact Email' }).press('Tab'); | ||
| await page.getByRole('textbox', { name: 'Contact Phone' }).fill('6475555554'); | ||
| await page.getByRole('button', { name: 'Save' }).click(); | ||
|
|
||
|
|
||
| // once the program is created, we need to find it on the program page and EDIT it, | ||
| // because at this time there is no "add items" for part when creating a new program | ||
| // (only when editing it) | ||
|
Comment on lines
+147
to
+149
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this is an intentional part of the New Program form, or if the ability to add items should be possible in that form. |
||
| await page.getByRole('link', { name: programName, exact: true }).click(); | ||
| await page.getByRole('link', { name: 'Edit', exact: true }).click(); | ||
| await page.getByRole('link', { name: 'New Item' }).click(); | ||
| await page.locator('#program-form_program_0_items_0_name').click(); | ||
| await page.locator('#program-form_program_0_items_0_name').fill('An item'); | ||
| await page.locator('#program-form_program_0_items_0_name').press('Tab'); | ||
| await page.locator('#program-form_program_0_items_0_description').fill('5 lbs'); | ||
| await page.getByRole('cell', { name: 'Foodshare Box' }).getByLabel('').selectOption('Food Hamper'); | ||
| await page.getByRole('button', { name: 'Save' }).click(); | ||
| // return to programs because otherwise we'll be on programs/<program_id> | ||
| await page.goto('http://localhost:4000/programs'); | ||
|
|
||
| } | ||
|
|
||
| async function createCampaign({ page, programName, numDays }: any) { | ||
| await page.goto('http://localhost:4000/campaigns/new'); | ||
| await page.waitForSelector("body > .phx-connected") | ||
| await page.getByRole('textbox', { name: 'Delivery Date' }).fill(getDatePlusDays(numDays)); | ||
|
|
||
| const programSelector = page.locator('#user-form_program_id') | ||
| await programSelector.selectOption({ label: programName }) | ||
|
|
||
|
|
||
| await page.locator('#location-form-location-input-open').click(); | ||
| await page.locator('#location-form-location-input-open').pressSequentially("200 Yonge", { delay: 200 }) | ||
| await page.getByRole('button', { name: 'Save' }).click(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // tests/helpers/sandbox.js | ||
| import { request, test as base } from '@playwright/test'; | ||
|
|
||
| async function setupSandbox(context: any) { | ||
| // Create sandbox | ||
| const requestContext = await request.newContext(); | ||
| const response = await requestContext.post('http://localhost:4000/sandbox', { | ||
| headers: { | ||
| 'Cache-Control': 'no-store' | ||
| } | ||
| }); | ||
|
|
||
| const sessionId = await response.text(); | ||
|
|
||
| // Set up the route interception to add sessionId to all requests | ||
| await context.route('**/*', async (route: any, request: any) => { | ||
| const headers = request.headers(); | ||
| headers['x-session-id'] = sessionId; | ||
| await route.continue({ headers }); | ||
| }); | ||
|
|
||
| // Store the sessionId for LiveView connections | ||
| await context.addInitScript(({ sessionId }) => { | ||
| window.sessionId = sessionId; | ||
| }, { sessionId }); | ||
|
|
||
| return sessionId; | ||
| } | ||
|
|
||
| async function teardownSandbox(sessionId: any) { | ||
| const requestContext = await request.newContext(); | ||
| await requestContext.delete('http://localhost:4000/sandbox', { | ||
| headers: { | ||
| 'x-session-id': sessionId | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| // module.exports = { setupSandbox, teardownSandbox }; | ||
|
|
||
|
|
||
|
|
||
| const test = base.extend({ | ||
| context: async ({ context }, use) => { | ||
| const sessionId = await setupSandbox(context); | ||
| console.log("sessionId is >>>>>>>>>>: ", sessionId) | ||
| await use(context); | ||
| await teardownSandbox(sessionId); | ||
| } | ||
| }); | ||
|
|
||
| const expect = base.expect | ||
|
|
||
| export { | ||
| test, | ||
| expect | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was required in order to add "label" attributes to options in the
<select>tag; these labels are used to traget dropdowns in tests.