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({