220 lines
11 KiB
TypeScript
220 lines
11 KiB
TypeScript
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: Boot · 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: Boot · 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: [],
|
||
minKnownDepthM: null,
|
||
unknownDepthRatio: 1,
|
||
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();
|
||
});
|