Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions tests/dataResearch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect, setupTest, test } from './utils'

test.describe('Data Research Page Tests', () => {
test.beforeEach(async ({ page }) => {
await setupTest(page)
})

test('accessing /data-research directly works', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
await expect(page).toHaveURL(/data-research/)
})

test('page displays research section heading', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
const title = page.locator('h2', { hasText: 'מחקרים' })
await expect(title).toBeVisible()
})

test('page displays research description text', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
await expect(
page.getByText('אם יש לכם רעיון מעניין למה קורים פה דברים, דברו איתנו בסלאק!'),
).toBeVisible()
})

test('stacked research section with charts is rendered', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
const etlWidget = page.locator('h2', { hasText: 'בעיות etl/gps/משהו גלובאלי אחר' })
await expect(etlWidget).toBeVisible()
})

test('research page has date selectors and operator selector', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
Comment on lines +6 to +43

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file repeats the same navigation + waiting sequence in multiple tests, which will significantly slow the suite and increases surface area for flakes. Since these tests all target the same route, consider moving the goto('/data-research') + wait logic into beforeEach (or a shared helper like visitPage) and keep tests focused on assertions.

Suggested change
})
test('accessing /data-research directly works', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
await expect(page).toHaveURL(/data-research/)
})
test('page displays research section heading', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
const title = page.locator('h2', { hasText: 'מחקרים' })
await expect(title).toBeVisible()
})
test('page displays research description text', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
await expect(
page.getByText('אם יש לכם רעיון מעניין למה קורים פה דברים, דברו איתנו בסלאק!'),
).toBeVisible()
})
test('stacked research section with charts is rendered', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
const etlWidget = page.locator('h2', { hasText: 'בעיות etl/gps/משהו גלובאלי אחר' })
await expect(etlWidget).toBeVisible()
})
test('research page has date selectors and operator selector', async ({ page }) => {
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
await page.goto('/data-research')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await page.waitForLoadState('networkidle')
})
test('accessing /data-research directly works', async ({ page }) => {
await expect(page).toHaveURL(/data-research/)
})
test('page displays research section heading', async ({ page }) => {
const title = page.locator('h2', { hasText: 'מחקרים' })
await expect(title).toBeVisible()
})
test('page displays research description text', async ({ page }) => {
await expect(
page.getByText('אם יש לכם רעיון מעניין למה קורים פה דברים, דברו איתנו בסלאק!'),
).toBeVisible()
})
test('stacked research section with charts is rendered', async ({ page }) => {
const etlWidget = page.locator('h2', { hasText: 'בעיות etl/gps/משהו גלובאלי אחר' })
await expect(etlWidget).toBeVisible()
})
test('research page has date selectors and operator selector', async ({ page }) => {

Copilot uses AI. Check for mistakes.
const startDateGroup = page.getByRole('group', { name: 'התחלה' }).first()
const endDateGroup = page.getByRole('group', { name: 'סיום' }).first()
await expect(startDateGroup).toBeVisible()
await expect(endDateGroup).toBeVisible()
})
})
48 changes: 48 additions & 0 deletions tests/donate.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import i18next from 'i18next'
import { expect, setupTest, test } from './utils'

test.describe('Donate Modal Tests', () => {
test.beforeEach(async ({ page }) => {
await setupTest(page)
})

test('clicking donate menu item opens the modal', async ({ page }) => {
const donateLink = page.locator('li a', { hasText: i18next.t('donate_title') })
await donateLink.click()
const modal = page.getByRole('dialog')
await expect(modal).toBeVisible()
await expect(
page.getByRole('heading', { name: i18next.t('how_to_donate_title') }),
).toBeVisible()
})

test('modal contains donation link to jgive.com', async ({ page }) => {
const donateLink = page.locator('li a', { hasText: i18next.t('donate_title') })
await donateLink.click()
const modal = page.getByRole('dialog')
await expect(modal).toBeVisible()
const jgiveLink = modal.locator('a[href*="jgive.com"]').first()
await expect(jgiveLink).toBeVisible()
await expect(jgiveLink).toHaveAttribute('href', /jgive\.com/)
})

test('modal contains bank transfer details', async ({ page }) => {
const donateLink = page.locator('li a', { hasText: i18next.t('donate_title') })
await donateLink.click()
const modal = page.getByRole('dialog')
await expect(modal).toBeVisible()
await expect(modal.getByText(i18next.t('donation_through_bank_title'))).toBeVisible()
await expect(modal.getByText(i18next.t('donation_through_bank_details_account'))).toBeVisible()
})

test('modal can be closed', async ({ page }) => {
const donateLink = page.locator('li a', { hasText: i18next.t('donate_title') })
await donateLink.click()
const modal = page.getByRole('dialog')
await expect(modal).toBeVisible()
// Close button is the X button inside the modal
const closeButton = modal.locator('button').first()
Comment on lines +43 to +44

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Selecting the “first button” in the modal is fragile (button order changes easily). Prefer locating the close control by accessible name/label (e.g., getByRole('button', { name: ... })) or a stable attribute (like aria-label="Close"), so the test targets the intended control.

Suggested change
// Close button is the X button inside the modal
const closeButton = modal.locator('button').first()
// Target the modal's close control by semantics instead of button order
const closeButton = modal.getByRole('button', { name: /close/i })

Copilot uses AI. Check for mistakes.
await closeButton.click()
await expect(modal).toBeHidden()
})
})
31 changes: 31 additions & 0 deletions tests/gaps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import i18next from 'i18next'
import { expect, harOptions, setupTest, test, visitPage } from './utils'

test.describe('Gaps Page Tests', () => {
test.beforeEach(async ({ page, advancedRouteFromHAR }) => {
await setupTest(page)
await advancedRouteFromHAR('tests/HAR/missing.har', harOptions)
await visitPage(page, 'gaps_page_title')
})

test('page title is visible', async ({ page }) => {
await expect(page.locator('h4')).toHaveText(i18next.t('gaps_page_title'))
})

test('page description alert is visible', async ({ page }) => {
await expect(page.getByText(i18next.t('gaps_page_description'))).toBeVisible()
})

test('date selector is present', async ({ page }) => {
const dateInput = page.locator('input[type="text"]').first()
await expect(dateInput).toBeVisible()
})

test('operator selector is present', async ({ page }) => {
await expect(page.getByLabel(i18next.t('choose_operator'))).toBeVisible()
})

test('line number selector is present', async ({ page }) => {
await expect(page.getByLabel(i18next.t('choose_line'))).toBeVisible()
})
})
33 changes: 33 additions & 0 deletions tests/gapsPatterns.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import i18next from 'i18next'
import { expect, fillDateField, harOptions, setupTest, test, visitPage } from './utils'

test.describe('Gaps Patterns Page Tests', () => {
test.beforeEach(async ({ page, advancedRouteFromHAR }) => {
await setupTest(page)
await advancedRouteFromHAR('tests/HAR/patterns.har', harOptions)
await visitPage(page, 'gaps_patterns_page_title')
})

test('page heading is displayed', async ({ page }) => {
await expect(page.locator('h4')).toContainText(i18next.t('gaps_patterns_page_title'))
})

test('page description is visible', async ({ page }) => {
await expect(page.getByText(i18next.t('gaps_patterns_page_description'))).toBeVisible()
})

test('start and end date selectors are present', async ({ page }) => {
const dateInputs = page.locator('input[type="text"]')
const count = await dateInputs.count()
expect(count).toBeGreaterThanOrEqual(2)
})

test('date selectors accept input', async ({ page }) => {
await fillDateField(page, 'התחלה', '01/02/2024')
await fillDateField(page, 'סיום', '08/02/2024')
})

test('operator selector is present', async ({ page }) => {
await expect(page.getByLabel(i18next.t('choose_operator'))).toBeVisible()
})
})
44 changes: 44 additions & 0 deletions tests/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import i18next from 'i18next'
import { expect, setupTest, test } from './utils'

test.describe('Homepage Tests', () => {
test.beforeEach(async ({ page }) => {
await setupTest(page)
await page.waitForLoadState('networkidle')
})

test('homepage displays welcome heading', async ({ page }) => {
await expect(page.getByRole('heading', { name: i18next.t('homepage.welcome') })).toBeVisible()
})

test('homepage displays definition', async ({ page }) => {
await expect(page.locator('h2').last()).toContainText(i18next.t('homepage.databus_definition'))
})

test('homepage displays bus illustration', async ({ page }) => {
const img = page.locator('img[alt="Public Transportaion Bus Illustration"]')

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selector contains a likely typo in the alt text ("Transportaion"). If the actual UI text is corrected, this test will fail. Consider using the correct spelling (or a less brittle selector, e.g., regex / translation-based alt text) to avoid coupling the test to a misspelling.

Suggested change
const img = page.locator('img[alt="Public Transportaion Bus Illustration"]')
const img = page.getByAltText(/Public Transporta(?:tion|ion) Bus Illustration/)

Copilot uses AI. Check for mistakes.
await expect(img).toBeVisible()
})

test('homepage displays navigation links to main pages', async ({ page }) => {
const links = page.locator('section.links .page-link')
const count = await links.count()
expect(count).toBe(6)
Comment on lines +24 to +26

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserting an exact link count is brittle and will fail on legitimate UI changes (adding/removing a link, A/B tests, responsive variants). A more stable approach is to assert the presence of specific expected links (by role/name) or to check a minimum count if the exact number isn’t a strict product requirement.

Suggested change
const links = page.locator('section.links .page-link')
const count = await links.count()
expect(count).toBe(6)
const linksSection = page.locator('section.links')
await expect(linksSection.getByRole('link', { name: i18next.t('nav.home') })).toBeVisible()
await expect(linksSection.getByRole('link', { name: i18next.t('nav.routes') })).toBeVisible()
await expect(linksSection.getByRole('link', { name: i18next.t('nav.stops') })).toBeVisible()
await expect(linksSection.getByRole('link', { name: i18next.t('nav.map') })).toBeVisible()
await expect(linksSection.getByRole('link', { name: i18next.t('nav.about') })).toBeVisible()
await expect(linksSection.getByRole('link', { name: i18next.t('nav.contact') })).toBeVisible()

Copilot uses AI. Check for mistakes.
})

test('homepage displays copyright footer', async ({ page }) => {
const footer = page.locator('footer')
await expect(footer).toBeVisible()
await expect(footer).toContainText(i18next.t('homepage.copyright'))
})

test('mobile menu link is hidden on desktop', async ({ page }) => {
const mobileSection = page.locator('section.hideOnDesktop')
await expect(mobileSection).toBeHidden()
})

test('desktop links section is visible on desktop', async ({ page }) => {
const desktopSection = page.locator('section.hideOnMobile')
await expect(desktopSection).toBeVisible()
})
})
13 changes: 13 additions & 0 deletions tests/lineProfile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expect, setupTest, test } from './utils'

test.describe('Line Profile Page Tests', () => {
test.beforeEach(async ({ page }) => {
await setupTest(page)
})

test('navigating to a profile route loads without error', async ({ page }) => {
await page.goto('/profile/1')
await page.locator('.preloader').waitFor({ state: 'hidden' })
await expect(page).toHaveURL(/\/profile\/1/)
})
})
17 changes: 17 additions & 0 deletions tests/publicAppealFull.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expect, setupTest, test, visitPage } from './utils'

test.describe('Public Appeal Page Tests', () => {
test.beforeEach(async ({ page }) => {
await setupTest(page)
await visitPage(page, 'public_appeal_title')
})

test('page renders without error', async ({ page }) => {
await expect(page).toHaveURL(/\/public-appeal/)
})

test('page has proper RTL layout', async ({ page }) => {
const dir = await page.locator('html').getAttribute('dir')
expect(dir).toBe('rtl')
})
})
69 changes: 69 additions & 0 deletions tests/velocityHeatmap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import i18next from 'i18next'
import { expect, setupTest, test, visitPage } from './utils'

test.describe('Velocity Heatmap Page Tests', () => {
test.beforeEach(async ({ page }) => {
await setupTest(page)
await visitPage(page, 'velocity_heatmap_page_title')
})

test('page displays heading and date controls', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Velocity Aggregation Heatmap' })).toBeVisible()

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion hardcodes an English heading string while the rest of the spec uses i18next keys. If the app runs under a non-English locale (or the copy changes), this will break. Prefer asserting with i18next.t(...) (or by checking a translated key that visitPage already targets) to keep the test aligned with the i18n approach used elsewhere.

Suggested change
await expect(page.getByRole('heading', { name: 'Velocity Aggregation Heatmap' })).toBeVisible()
await expect(
page.getByRole('heading', { name: i18next.t('velocity_heatmap_page_title') }),
).toBeVisible()

Copilot uses AI. Check for mistakes.
})

test('page displays visualization mode radio buttons', async ({ page }) => {
const radios = page.getByRole('radio')
await expect(radios).toHaveCount(3)
await expect(radios.first()).toBeChecked()
})

test('switching visualization mode updates the selected radio', async ({ page }) => {
const radios = page.getByRole('radio')
await radios.nth(1).click()
await expect(radios.nth(1)).toBeChecked()
await expect(radios.first()).not.toBeChecked()
})

test('page displays a map container', async ({ page }) => {
const mapContainer = page.locator('.leaflet-container')
await expect(mapContainer).toBeVisible()
})

test('map has expand button', async ({ page }) => {
const expandButton = page.locator('.expand-button')
await expect(expandButton).toBeVisible()
})

test('date navigation buttons are present', async ({ page }) => {
await expect(
page.getByRole('button', { name: i18next.t('date_navigator_prev_day') }),
).toBeVisible()
await expect(
page.getByRole('button', { name: i18next.t('date_navigator_next_day') }),
).toBeVisible()
await expect(
page.getByRole('button', { name: i18next.t('date_navigator_prev_week') }),
).toBeVisible()
await expect(
page.getByRole('button', { name: i18next.t('date_navigator_next_week') }),
).toBeVisible()
await expect(
page.getByRole('button', { name: i18next.t('date_navigator_today') }),
).toBeVisible()
})

test('clicking prev/next day buttons changes the date', async ({ page }) => {
const dateInput = page.locator('input.MuiPickersInputBase-input').first()
const initialValue = await dateInput.inputValue()

await page.getByRole('button', { name: i18next.t('date_navigator_prev_day') }).click()
await page.waitForTimeout(300)
const afterPrevDay = await dateInput.inputValue()
expect(afterPrevDay).not.toEqual(initialValue)

await page.getByRole('button', { name: i18next.t('date_navigator_next_day') }).click()
await page.waitForTimeout(300)
const afterNextDay = await dateInput.inputValue()
expect(afterNextDay).toEqual(initialValue)
Comment on lines +60 to +67

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

page.waitForTimeout(300) makes this test timing-dependent and flaky under slow CI. Prefer waiting on an explicit condition (e.g., await expect(dateInput).not.toHaveValue(initialValue) after prev-day click, and then await expect(dateInput).toHaveValue(initialValue) after next-day click), or use expect.poll on inputValue() so Playwright waits until the UI updates.

Suggested change
await page.waitForTimeout(300)
const afterPrevDay = await dateInput.inputValue()
expect(afterPrevDay).not.toEqual(initialValue)
await page.getByRole('button', { name: i18next.t('date_navigator_next_day') }).click()
await page.waitForTimeout(300)
const afterNextDay = await dateInput.inputValue()
expect(afterNextDay).toEqual(initialValue)
await expect(dateInput).not.toHaveValue(initialValue)
await page.getByRole('button', { name: i18next.t('date_navigator_next_day') }).click()
await expect(dateInput).toHaveValue(initialValue)

Copilot uses AI. Check for mistakes.
})
})
Loading