trails/e2e/komoot-import.test.ts
Ullrich Schäfer 25f7d35e03
Fix E2E: associate labels with inputs via htmlFor/id; increase test timeouts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:49:18 +02:00

267 lines
9.1 KiB
TypeScript

import { test, expect, waitForHydration, type CDPSession, type Page } from "./fixtures/test";
const KOMOOT_USER_ID = "99999999999";
const KOMOOT_PROFILE_URL = `https://www.komoot.com/user/${KOMOOT_USER_ID}`;
const SAMPLE_GPX = `<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="komoot" xmlns="http://www.topografix.com/GPX/1/1">
<trk>
<name>Morning Hike</name>
<trkseg>
<trkpt lat="48.137" lon="11.575"><ele>520</ele></trkpt>
<trkpt lat="48.138" lon="11.576"><ele>525</ele></trkpt>
<trkpt lat="48.139" lon="11.577"><ele>530</ele></trkpt>
</trkseg>
</trk>
</gpx>`;
const MOCK_TOURS_PAGE = {
_embedded: {
tours: [
{
id: 111111111,
name: "Morning Hike",
type: "hike",
date: "2024-06-01T08:00:00.000Z",
distance: 12500,
duration: 7200,
elevation_up: 350,
elevation_down: 350,
},
],
},
page: { totalPages: 1, number: 0 },
};
async function setupVirtualAuthenticator(cdp: CDPSession) {
await cdp.send("WebAuthn.enable");
const { authenticatorId } = await cdp.send("WebAuthn.addVirtualAuthenticator", {
options: {
protocol: "ctap2",
transport: "internal",
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true,
},
});
return authenticatorId;
}
async function removeVirtualAuthenticator(cdp: CDPSession, authenticatorId: string) {
await cdp.send("WebAuthn.removeVirtualAuthenticator", { authenticatorId });
await cdp.send("WebAuthn.disable");
}
async function registerAndLogin(page: Page, cdp: CDPSession) {
const ts = Date.now();
const email = `komoot-${ts}@example.com`;
const username = `komootuser${ts}`;
await page.goto("/auth/register");
await page.getByLabel("Email").fill(email);
await page.getByLabel("Username").fill(username);
await page.getByRole("checkbox").check();
await page.getByRole("button", { name: /Register with Passkey/ }).click();
await expect(page).toHaveURL("/", { timeout: 10000 });
return { email, username };
}
function mockKomootPublicProfile(page: Page, userId: string, trailsProfileUrl: string) {
return page.route(`https://api.komoot.de/v007/users/${userId}/`, (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
username: userId,
display_name: "Test Komoot User",
content: {
text: `Check out my trails profile: ${trailsProfileUrl}`,
link: null,
},
}),
}),
);
}
function mockKomootPublicProfileNoMatch(page: Page, userId: string) {
return page.route(`https://api.komoot.de/v007/users/${userId}/`, (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
username: userId,
display_name: "Test Komoot User",
content: { text: "I like hiking", link: null },
}),
}),
);
}
function mockKomootTours(page: Page, userId: string) {
return page.route(
(url) => url.href.includes(`api.komoot.de/v007/users/${userId}/tours`),
(route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(MOCK_TOURS_PAGE),
}),
);
}
function mockKomootGpx(page: Page, tourId: string) {
return page.route(
(url) => url.href.includes(`api.komoot.de/v007/tours/${tourId}.gpx`),
(route) =>
route.fulfill({
status: 200,
contentType: "application/gpx+xml",
body: SAMPLE_GPX,
}),
);
}
test.describe("Komoot connection page", () => {
test.setTimeout(60000);
test("unauthenticated user is redirected to login", async ({ page }) => {
await page.goto("/settings/connections/komoot");
await expect(page).toHaveURL(/\/auth\/login/, { timeout: 5000 });
});
test("public verify — success path", async ({ page }) => {
const cdp = await page.context().newCDPSession(page);
const authenticatorId = await setupVirtualAuthenticator(cdp);
const { username } = await registerAndLogin(page, cdp);
const trailsProfileUrl = `http://localhost:3000/users/${username}`;
await mockKomootPublicProfile(page, KOMOOT_USER_ID, trailsProfileUrl);
await page.goto("/settings/connections/komoot");
await waitForHydration(page);
await page.getByLabel("Komoot profile URL or user ID").fill(KOMOOT_PROFILE_URL);
await page.getByRole("button", { name: "Verify" }).click();
await expect(page.getByRole("status")).toContainText("connected in public mode", {
timeout: 10000,
});
await removeVirtualAuthenticator(cdp, authenticatorId);
});
test("public verify — bio does not contain profile URL shows error", async ({ page }) => {
const cdp = await page.context().newCDPSession(page);
const authenticatorId = await setupVirtualAuthenticator(cdp);
await registerAndLogin(page, cdp);
await mockKomootPublicProfileNoMatch(page, KOMOOT_USER_ID);
await page.goto("/settings/connections/komoot");
await waitForHydration(page);
await page.getByLabel("Komoot profile URL or user ID").fill(KOMOOT_PROFILE_URL);
await page.getByRole("button", { name: "Verify" }).click();
await expect(page.getByRole("alert")).toContainText("Could not verify", { timeout: 10000 });
await removeVirtualAuthenticator(cdp, authenticatorId);
});
test("public verify — invalid URL shows error", async ({ page }) => {
const cdp = await page.context().newCDPSession(page);
const authenticatorId = await setupVirtualAuthenticator(cdp);
await registerAndLogin(page, cdp);
await page.goto("/settings/connections/komoot");
await waitForHydration(page);
await page.getByLabel("Komoot profile URL or user ID").fill("not-a-valid-url");
await page.getByRole("button", { name: "Verify" }).click();
await expect(page.getByRole("alert")).toContainText("valid Komoot", { timeout: 10000 });
await removeVirtualAuthenticator(cdp, authenticatorId);
});
test("authenticated connect — invalid credentials shows error", async ({ page }) => {
const cdp = await page.context().newCDPSession(page);
const authenticatorId = await setupVirtualAuthenticator(cdp);
await registerAndLogin(page, cdp);
// Mock Komoot auth endpoint to reject
await page.route(/api\.komoot\.de\/v006\/account\/email/, (route) =>
route.fulfill({ status: 401, body: "Unauthorized" }),
);
await page.goto("/settings/connections/komoot");
await waitForHydration(page);
await page.getByLabel("Komoot email").fill("wrong@example.com");
await page.getByLabel("Komoot password").fill("wrongpassword");
await page.getByRole("button", { name: "Connect" }).click();
await expect(page.getByRole("alert")).toContainText("Invalid Komoot", { timeout: 10000 });
await removeVirtualAuthenticator(cdp, authenticatorId);
});
});
test.describe("Komoot import page", () => {
test.setTimeout(90000);
test("shows importable public tours after public verify", async ({ page }) => {
const cdp = await page.context().newCDPSession(page);
const authenticatorId = await setupVirtualAuthenticator(cdp);
const { username } = await registerAndLogin(page, cdp);
const trailsProfileUrl = `http://localhost:3000/users/${username}`;
await mockKomootPublicProfile(page, KOMOOT_USER_ID, trailsProfileUrl);
await mockKomootTours(page, KOMOOT_USER_ID);
await mockKomootGpx(page, "111111111");
// Connect first
await page.goto("/settings/connections/komoot");
await waitForHydration(page);
await page.getByLabel("Komoot profile URL or user ID").fill(KOMOOT_PROFILE_URL);
await page.getByRole("button", { name: "Verify" }).click();
await expect(page.getByRole("status")).toContainText("connected in public mode", {
timeout: 10000,
});
// Navigate to import page
await page.goto("/sync/import/komoot");
await expect(page.getByText("Morning Hike")).toBeVisible({ timeout: 10000 });
await removeVirtualAuthenticator(cdp, authenticatorId);
});
test("import tour creates a route", async ({ page }) => {
const cdp = await page.context().newCDPSession(page);
const authenticatorId = await setupVirtualAuthenticator(cdp);
const { username } = await registerAndLogin(page, cdp);
const trailsProfileUrl = `http://localhost:3000/users/${username}`;
await mockKomootPublicProfile(page, KOMOOT_USER_ID, trailsProfileUrl);
await mockKomootTours(page, KOMOOT_USER_ID);
await mockKomootGpx(page, "111111111");
// Connect
await page.goto("/settings/connections/komoot");
await waitForHydration(page);
await page.getByLabel("Komoot profile URL or user ID").fill(KOMOOT_PROFILE_URL);
await page.getByRole("button", { name: "Verify" }).click();
await expect(page.getByRole("status")).toContainText("connected in public mode", {
timeout: 10000,
});
// Import
await page.goto("/sync/import/komoot");
await expect(page.getByText("Morning Hike")).toBeVisible({ timeout: 10000 });
await page.getByRole("button", { name: "Import" }).click();
await expect(page.getByText("Imported")).toBeVisible({ timeout: 10000 });
await removeVirtualAuthenticator(cdp, authenticatorId);
});
});