diff --git a/.github/workflows/playwright-scheduled-staging.yml b/.github/workflows/playwright-scheduled-staging.yml index fb6dafb2..d8a0ccb5 100644 --- a/.github/workflows/playwright-scheduled-staging.yml +++ b/.github/workflows/playwright-scheduled-staging.yml @@ -41,7 +41,7 @@ jobs: - name: Run Playwright tests run: yarn playwright test env: - NEXT_PUBLIC_PLANNER_ORG_ID: atb + NEXT_PUBLIC_PLANNER_ORG_ID: ${{ matrix.env }} E2E_URL: 'https://${{ matrix.env }}-staging.planner-web.mittatb.no/' - uses: actions/upload-artifact@v4 diff --git a/e2e-tests/assistant-view.spec.ts b/e2e-tests/assistant-view.spec.ts new file mode 100644 index 00000000..f80ce3e2 --- /dev/null +++ b/e2e-tests/assistant-view.spec.ts @@ -0,0 +1,79 @@ +import { test, expect } from '@playwright/test'; + +test.use({ + geolocation: { longitude: 62.4722, latitude: 6.1495 }, + permissions: ['geolocation'], +}); + +test('Should fetch Kristiansund - Molde and loading more after first result', async ({ + page, +}) => { + await page.goto(process.env.E2E_URL ?? 'http://localhost:3000'); + + await page.getByRole('textbox', { name: 'From' }).click(); + await page.getByRole('textbox', { name: 'From' }).fill('Kristiansund'); + await page + .getByRole('option', { name: 'Kristiansund Kristiansund', exact: true }) + .click(); + + await page.getByRole('textbox', { name: 'To' }).click(); + await page.getByRole('textbox', { name: 'To' }).fill('Molde'); + + const initialRequest = page.waitForResponse((request) => { + return request.url().includes('trip') && request.url().includes('cursor'); + }); + + await page.getByRole('option', { name: 'Molde Molde', exact: true }).click(); + + await initialRequest; + + await page.getByRole('button', { name: 'More choices' }).click(); + await page.getByText('Bus', { exact: true }).click(); + await page.getByRole('button', { name: 'Find departures' }).click(); + + const tripPatternItem = page.getByTestId('tripPattern-0-0'); + await tripPatternItem.waitFor(); + + const additionalRequest = page.waitForRequest((request) => { + return request.url().includes('trip') && request.url().includes('cursor'); + }); + await page.getByRole('button', { name: 'Load more results' }).click(); + + await additionalRequest; +}); + +test('should show non transit trips on walkable distance', async ({ page }) => { + await page.goto(process.env.E2E_URL ?? 'http://localhost:3000'); + await page.getByRole('textbox', { name: 'From' }).click(); + await page.getByRole('textbox', { name: 'From' }).fill('Fylkeshuset i Møre'); + await page + .getByRole('option', { name: 'Fylkeshuset i Møre og Romsdal' }) + .click(); + await page.getByRole('textbox', { name: 'To' }).click(); + await page.getByRole('textbox', { name: 'To' }).fill('Roseby'); + await page.getByRole('option', { name: 'Roseby Molde', exact: true }).click(); + + await page.getByTestId('non-transit-pill-foot').click(); + + await expect( + page.getByRole('heading', { name: 'Fylkeshuset i Møre og Romsdal' }), + ).toBeVisible(); +}); + +test('should show non transit trips on cyclable distance', async ({ page }) => { + await page.goto(process.env.E2E_URL ?? 'http://localhost:3000'); + await page.getByRole('textbox', { name: 'From' }).click(); + await page.getByRole('textbox', { name: 'From' }).fill('Fylkeshuset i Møre'); + await page + .getByRole('option', { name: 'Fylkeshuset i Møre og Romsdal' }) + .click(); + await page.getByRole('textbox', { name: 'To' }).click(); + await page.getByRole('textbox', { name: 'To' }).fill('Roseby'); + await page.getByRole('option', { name: 'Roseby Molde', exact: true }).click(); + + await page.getByTestId('non-transit-pill-bicycle').click(); + + await expect( + page.getByRole('heading', { name: 'Fylkeshuset i Møre og Romsdal' }), + ).toBeVisible(); +}); diff --git a/e2e-tests/fram-specific/assistant.spec.ts b/e2e-tests/fram-specific/assistant.spec.ts new file mode 100644 index 00000000..f7051bf4 --- /dev/null +++ b/e2e-tests/fram-specific/assistant.spec.ts @@ -0,0 +1,88 @@ +import { test, expect } from '@playwright/test'; + +test.use({ + geolocation: { longitude: 62.4722, latitude: 6.1495 }, + permissions: ['geolocation'], +}); + +test.describe('fram only', () => { + test.skip( + () => process.env.NEXT_PUBLIC_PLANNER_ORG_ID !== 'fram', + 'Only FRAM!', + ); + + test('Should filter on line 701', async ({ page }) => { + await page.goto(process.env.E2E_URL ?? 'http://localhost:3000'); + + await page.getByRole('textbox', { name: 'From' }).click(); + await page.getByRole('textbox', { name: 'From' }).fill('Kvam'); + await page.getByRole('option', { name: 'Kvam skole Molde' }).click(); + + await page.getByRole('textbox', { name: 'To' }).click(); + await page.getByRole('textbox', { name: 'To' }).fill('Fylkeshusa'); + + const tripResponse = page.waitForResponse((request) => { + return request.url().includes('assistant/trip'); + }); + await page.getByRole('option', { name: 'Fylkeshusa Molde' }).click(); + + await page.getByRole('button', { name: 'More choices' }).click(); + + await tripResponse; + + await page.getByPlaceholder('line number').click(); + await page.getByPlaceholder('line number').fill('701'); + + const tripResponse2 = page.waitForResponse((request) => { + return request.url().includes('assistant/trip'); + }); + await page.getByRole('button', { name: 'Find departures' }).click(); + await tripResponse2; + + const tripPatternItem2 = page.getByTestId('tripPattern-0-0'); + await tripPatternItem2.waitFor(); + + await expect(tripPatternItem2).toBeVisible(); + expect(await tripPatternItem2.getAttribute('aria-label')).toContain('701'); + }); + + test('should show boats and message on Correspondance', async ({ page }) => { + await page.goto(process.env.E2E_URL ?? 'http://localhost:3000'); + + await page.getByRole('textbox', { name: 'From' }).click(); + await page.getByRole('textbox', { name: 'From' }).fill('Moa trafikktermin'); + await page.getByRole('option', { name: 'Moa trafikkterminal' }).click(); + + await page.getByRole('textbox', { name: 'To' }).click(); + await page.getByRole('textbox', { name: 'To' }).fill('Ulsteinvik'); + + const additionalRequest = page.waitForResponse((request) => { + return request.url().includes('trip'); + }); + + await page + .getByRole('option', { name: 'Ulsteinvik Ulstein', exact: true }) + .click(); + + await additionalRequest; + + await page.getByTestId('tripPattern-0-0').waitFor(); + + await page.getByRole('button', { name: 'More choices' }).click(); + + const additionalRequest2 = page.waitForResponse((request) => { + return ( + request.url().includes('trip') && request.url().includes('expressboat') + ); + }); + await page.locator('label').filter({ hasText: 'Express boat' }).click(); + await page.getByText('Bus', { exact: true }).click(); + + await page.getByText('Loading travel suggestions...').waitFor(); + + await additionalRequest2; + + await page.getByText('1145').first().click(); + await expect(page.getByText('Correspondance between 1145')).toBeVisible(); + }); +}); diff --git a/e2e-tests/search-test.spec.ts b/e2e-tests/search-test.spec.ts deleted file mode 100644 index 936cff53..00000000 --- a/e2e-tests/search-test.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('Test fetching Kristiansund - Molde and loading more after first result', async ({ - page, -}) => { - await page.goto(process.env.E2E_URL ?? 'http://localhost:3000'); - - await page.getByRole('textbox', { name: 'From' }).click(); - await page.getByRole('textbox', { name: 'From' }).fill('Kristiansund'); - await page.getByRole('option', { name: 'Kristiansund Kristiansund' }).click(); - - await page.getByRole('textbox', { name: 'To' }).click(); - await page.getByRole('textbox', { name: 'To' }).fill('Molde'); - await page.getByRole('option', { name: 'Molde Molde', exact: true }).click(); - - await page.getByRole('button', { name: 'More choices' }).click(); - await page.getByText('Bus', { exact: true }).click(); - await page.getByRole('button', { name: 'Find departures' }).click(); - - const tripPatternItem = page.getByTestId('trip-pattern-0'); - await tripPatternItem.waitFor(); - - const additionalRequest = page.waitForRequest((request) => { - return request.url().includes('trip') && request.url().includes('cursor'); - }); - await page.getByRole('button', { name: 'Load more results' }).click(); - - await additionalRequest; -}); diff --git a/src/page-modules/assistant/__tests__/assistant.test.tsx b/src/page-modules/assistant/__tests__/assistant.test.tsx index b7fb364b..568afe86 100644 --- a/src/page-modules/assistant/__tests__/assistant.test.tsx +++ b/src/page-modules/assistant/__tests__/assistant.test.tsx @@ -253,6 +253,7 @@ describe('assistant page', function () { transportModeFilter: null, cursor: null, lineFilter: null, + via: null, }; const fromToTripQuery2: FromToTripQuery = { from: fromFeature, @@ -286,6 +287,7 @@ describe('assistant page', function () { transportModeFilter: null, cursor: null, lineFilter: null, + via: null, }; addAssistantTripToCache(cachedFromToTripQuery, tripResult); diff --git a/src/page-modules/assistant/non-transit-pill/index.tsx b/src/page-modules/assistant/non-transit-pill/index.tsx index 65bd73da..4d38d0ca 100644 --- a/src/page-modules/assistant/non-transit-pill/index.tsx +++ b/src/page-modules/assistant/non-transit-pill/index.tsx @@ -31,6 +31,7 @@ export function NonTransitTrip({ nonTransit }: NonTransitTripProps) { size="pill" href={`/assistant/${nonTransit.compressedQuery}`} title={`${modeText} ${durationShort}`} + testID={`non-transit-pill-${mode}`} icon={{ left: , right: , diff --git a/src/page-modules/assistant/server/trip-cache.ts b/src/page-modules/assistant/server/trip-cache.ts index 22e5d0c9..5b01df09 100644 --- a/src/page-modules/assistant/server/trip-cache.ts +++ b/src/page-modules/assistant/server/trip-cache.ts @@ -1,6 +1,5 @@ import TTLCache from '@isaacs/ttlcache'; import { FromToTripQuery, TripData } from '../types'; -import { createTripQuery, tripQueryToQueryString } from '../utils'; let tripCache: TTLCache | null = null; @@ -15,9 +14,9 @@ function getTripCacheInstance(): TTLCache { export function getAssistantTripIfCached( query: FromToTripQuery, ): TripData | undefined { - const queryString = tripQueryToQueryString(createTripQuery(query)); - if (tripCache?.has(`/api/assistant/trip?${queryString}`)) { - return tripCache.get(`/api/assistant/trip?${queryString}`); + const cacheKey = createCacheKey(query); + if (tripCache?.has(cacheKey)) { + return tripCache.get(cacheKey); } } @@ -25,6 +24,15 @@ export function addAssistantTripToCache( query: FromToTripQuery, tripData: TripData, ) { - const queryString = tripQueryToQueryString(createTripQuery(query)); - getTripCacheInstance().set(`/api/assistant/trip?${queryString}`, tripData); + getTripCacheInstance().set(createCacheKey(query), tripData); +} + +function createCacheKey(valuesToCreateCacheKey: FromToTripQuery) { + const keys = Object.keys(valuesToCreateCacheKey).sort(); + const values = keys.map( + (key) => valuesToCreateCacheKey[key as keyof FromToTripQuery], + ); + const cacheKey = JSON.stringify(values); + + return 'assistant-trip-' + Buffer.from(cacheKey).toString('base64'); } diff --git a/src/page-modules/assistant/trip/index.tsx b/src/page-modules/assistant/trip/index.tsx index b53cde53..c0be6222 100644 --- a/src/page-modules/assistant/trip/index.tsx +++ b/src/page-modules/assistant/trip/index.tsx @@ -100,6 +100,7 @@ export default function Trip({ tripQuery, fallback }: TripProps) { tripPattern={tripPattern} delay={i * 0.1} index={i} + testId={`tripPattern-${tripIndex}-${i}`} /> )), diff --git a/src/page-modules/assistant/trip/trip-pattern/index.tsx b/src/page-modules/assistant/trip/trip-pattern/index.tsx index df2ab95d..029da9c4 100644 --- a/src/page-modules/assistant/trip/trip-pattern/index.tsx +++ b/src/page-modules/assistant/trip/trip-pattern/index.tsx @@ -19,12 +19,14 @@ type TripPatternProps = { tripPattern: TripPatternType; delay: number; index: number; + testId?: string; }; export default function TripPattern({ tripPattern, delay, index, + testId, }: TripPatternProps) { const { t, language } = useTranslation(); @@ -70,7 +72,7 @@ export default function TripPattern({