import { expect, test } from "@playwright/test"; test("renders the iPhone PWA shell without overlapping core controls", async ({ page, context }) => { await context.grantPermissions(["geolocation"]); await context.setGeolocation({ latitude: 54.18, longitude: 12.09 }); await page.goto("/"); await page.getByRole("button", { name: "GPS starten" }).click(); await expect(page.getByText("Watermaps")).toBeVisible(); await expect(page.getByTestId("map-container")).toBeVisible(); await expect(page.getByRole("button", { name: "Layer" })).toBeVisible(); await expect(page.getByRole("complementary", { name: "Routenplanung" })).toBeVisible(); await expect(page.getByRole("button", { name: "Start auf Karte setzen" })).toBeVisible(); await expect(page.getByRole("button", { name: "Ziel auf Karte setzen" })).toBeVisible(); await page.getByText("Routenoptionen: Zwischenziele").click(); await expect(page.getByRole("button", { name: "Zwischenziel auf der Karte hinzufügen" })).toBeVisible(); await expect(page.getByLabel("Abfahrt")).toBeVisible(); await page.getByText("Unterwegs: GPX · Offline · Kursalarm").click(); await expect(page.getByRole("region", { name: "Navigation und Offline-Route" })).toBeVisible(); await page.getByRole("button", { name: "Layer" }).click(); await expect(page.getByLabel("Layer Auswahl").getByText("Brücken")).toBeVisible(); await expect(page.getByLabel("Layer Auswahl").getByText("Tiefen")).toBeVisible(); await expect(page.getByLabel("Layer Auswahl").getByText("Schleusen")).toBeVisible(); await expect(page.getByLabel("Layer Auswahl").getByText("Häfen")).toBeVisible(); }); test("sets start and destination only after explicit map picking actions", async ({ page, context }) => { await context.grantPermissions(["geolocation"]); await context.setGeolocation({ latitude: 54.18, longitude: 12.09 }); await page.goto("/"); await expect(page.getByText("Start und Ziel setzen")).toBeVisible(); await expect(page.getByRole("button", { name: "Layer" })).toBeVisible(); const mapBox = await page.getByTestId("map-container").boundingBox(); if (!mapBox) { throw new Error("Map container not visible"); } const mapTapY = mapBox.y + Math.min(132, mapBox.height * 0.2); await page.touchscreen.tap(mapBox.x + mapBox.width / 2, mapTapY); await expect(page.getByText("Start und Ziel setzen")).toBeVisible(); await page.getByRole("button", { name: "Start auf Karte setzen" }).click(); await expect(page.getByText("Startpunkt auf der Karte anklicken")).toBeVisible(); await page.touchscreen.tap(mapBox.x + mapBox.width / 3, mapTapY); await expect(page.getByText(/^Start \d/)).toBeVisible(); await page.getByRole("button", { name: "Ziel auf Karte setzen" }).click(); await expect(page.getByText("Ziel auf der Karte anklicken")).toBeVisible(); await page.touchscreen.tap(mapBox.x + (mapBox.width * 2) / 3, mapTapY); await expect(page.getByText(/^Ziel \d/)).toBeVisible(); await expect(page.getByText("Route bereit zur Prüfung")).toBeVisible(); await page.getByText("Routenoptionen: Zwischenziele").click(); await page.getByRole("button", { name: "Zwischenziel auf der Karte hinzufügen" }).click(); await expect(page.getByText("Zwischenziel auf der Karte anklicken")).toBeVisible(); await page.touchscreen.tap(mapBox.x + mapBox.width / 2, mapTapY); await expect(page.getByText(/^Z1 /)).toBeVisible(); }); test("does not expose Borkum or Hamm as direct route shortcuts", async ({ page }) => { await page.goto("/"); await expect(page.getByRole("button", { name: /Borkum/i })).toHaveCount(0); await expect(page.getByRole("button", { name: /Hamm/i })).toHaveCount(0); await expect(page.getByRole("button", { name: "Start auf Karte setzen" })).toBeVisible(); await expect(page.getByRole("button", { name: "Ziel auf Karte setzen" })).toBeVisible(); }); test("can hide and restore the route planner for a map-only view", async ({ page }) => { await page.goto("/"); await page.getByRole("button", { name: "Routenfenster ausblenden" }).click(); await expect(page.getByRole("complementary", { name: "Routenplanung" })).toBeHidden(); await expect(page.getByRole("button", { name: "Routenfenster einblenden" })).toBeVisible(); await expect(page.getByRole("button", { name: "Start auf Karte setzen" })).toBeHidden(); await page.getByRole("button", { name: "Routenfenster einblenden" }).click(); await expect(page.getByRole("complementary", { name: "Routenplanung" })).toBeVisible(); await expect(page.getByRole("button", { name: "Start auf Karte setzen" })).toBeVisible(); }); test("cycles the mobile route sheet through half, full, and compact heights", async ({ page }) => { await page.goto("/"); const routePanel = page.getByRole("complementary", { name: "Routenplanung" }); await expect(routePanel).toHaveAttribute("data-sheet-state", "half"); const halfBox = await routePanel.boundingBox(); await page .getByRole("button", { name: "Routenfenster auf volle Höhe vergrößern" }) .click(); await expect(routePanel).toHaveAttribute("data-sheet-state", "full"); const fullBox = await routePanel.boundingBox(); expect(fullBox && halfBox && fullBox.height > halfBox.height + 80).toBe(true); await page .getByRole("button", { name: "Routenfenster auf kompakte Höhe verkleinern" }) .click(); await expect(routePanel).toHaveAttribute("data-sheet-state", "compact"); const compactBox = await routePanel.boundingBox(); expect(compactBox && halfBox && compactBox.height < halfBox.height - 80).toBe(true); await expect(page.getByRole("button", { name: "Start auf Karte setzen" })).toBeHidden(); await page.setViewportSize({ width: 900, height: 844 }); await expect(routePanel).toHaveAttribute("data-sheet-state", "half"); await expect(page.getByRole("button", { name: "Start auf Karte setzen" })).toBeVisible(); }); test("starts a visible dynamic course assistant only after route planning", async ({ page, context }) => { await context.grantPermissions(["geolocation"]); await context.setGeolocation({ latitude: 54.18, longitude: 12.09 }); await page.route("**/api/routes", async (request) => { await request.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "guidance-e2e", name: "Testkurs Ost", geometry: { type: "LineString", coordinates: [[12.09, 54.18], [12.11, 54.18]] }, distanceNm: 0.7, eta: null, warnings: [], dataSources: ["E2E"], routingMode: "fairway" }) }); }); await page.goto("/"); await expect(page.getByRole("button", { name: "Layer" })).toBeVisible(); const mapBox = await page.getByTestId("map-container").boundingBox(); if (!mapBox) throw new Error("Map container not visible"); const tapY = mapBox.y + Math.min(132, mapBox.height * 0.2); await page.getByRole("button", { name: "Start auf Karte setzen" }).click(); await page.touchscreen.tap(mapBox.x + mapBox.width / 3, tapY); await page.getByRole("button", { name: "Ziel auf Karte setzen" }).click(); await page.touchscreen.tap(mapBox.x + (mapBox.width * 2) / 3, tapY); await page.getByRole("button", { name: "Route berechnen" }).click(); const startAssistant = page.getByRole("button", { name: "Navigation starten" }); await expect(startAssistant).toBeVisible(); await startAssistant.click(); const assistant = page.getByRole("complementary", { name: "Kursassistent" }); await expect(assistant).toBeVisible(); await expect(assistant.getByText("SOLL ÜBER GRUND")).toBeVisible(); await expect(assistant.getByRole("button", { name: "Kursassistent stoppen" })).toBeVisible(); await expect(page.getByRole("complementary", { name: "Routenplanung" })).toBeHidden(); const assistantBox = await assistant.boundingBox(); const statusBox = await page.getByLabel("Navigationsstatus").boundingBox(); expect(assistantBox && statusBox && assistantBox.y + assistantBox.height <= statusBox.y + 1).toBe(true); }); test("sets and arms the anchor watch only through the explicit two-step flow", async ({ page, context }) => { const now = Date.now(); await context.grantPermissions(["geolocation"]); await context.setGeolocation({ latitude: 53.2159, longitude: 6.5766 }); await page.route("**/api/tides/nearest?*", async (request) => { await request.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ station: "Testpegel Emden", distanceKm: 4.2, nextHigh: null, nextLow: null, waterLevelCurve: [ { time: new Date(now - 60 * 60_000).toISOString(), predictedM: 1 }, { time: new Date(now + 6 * 60 * 60_000).toISOString(), predictedM: 2 }, { time: new Date(now + 30 * 60 * 60_000).toISOString(), predictedM: 0.5 } ], source: "E2E", updatedAt: new Date(now).toISOString() }) }); }); await page.goto("/"); await expect(page.getByRole("complementary", { name: "Ankerwache" })).toHaveCount(0); await page.getByRole("button", { name: "GPS starten" }).click(); await page.getByRole("button", { name: "Ankerwache öffnen" }).click(); const panel = page.getByRole("complementary", { name: "Ankerwache" }); await expect(panel).toBeVisible(); await expect(panel.getByText(/GPS ±\d+ m/)).toBeVisible(); await expect(page.getByRole("complementary", { name: "Routenplanung" })).toBeHidden(); await panel.getByRole("button", { name: "Anker gefallen – Position jetzt setzen" }).click(); await expect(panel.getByText("Ankerpunkt gespeichert")).toBeVisible(); await panel.getByLabel("Tiefe beim Setzen").fill("4"); await panel.getByLabel("Bugrolle über Wasser").fill("1"); await panel.getByLabel("Kette / Leine draußen").fill("40"); await panel.getByLabel("Gewähltes Verhältnis").selectOption("5"); await expect(panel.getByText(/Testpegel Emden · 4\.2 km entfernt/)).toBeVisible(); await expect(panel.getByText(/m Reserve/)).toBeVisible(); await panel.getByRole("button", { name: "Wache starten" }).click(); await expect(panel.getByText("Ankerwache aktiv")).toBeVisible(); await expect(panel.getByText("Im überwachten Schwojkreis")).toBeVisible(); const panelBox = await panel.boundingBox(); const statusBox = await page.getByLabel("Navigationsstatus").boundingBox(); expect(panelBox && statusBox && panelBox.y + panelBox.height <= statusBox.y + 1).toBe(true); await panel.getByRole("button", { name: "Ankerwache beenden" }).click(); await expect(panel.getByRole("button", { name: "Wirklich beenden" })).toBeVisible(); await panel.getByRole("button", { name: "Wirklich beenden" }).click(); await expect(panel).toBeHidden(); await expect(page.getByRole("complementary", { name: "Routenplanung" })).toBeVisible(); });