Rolle auswählen
diff --git a/pnpm-monorepo/apps/app/src/modules/users/components/UsersTable.tsx b/pnpm-monorepo/apps/app/src/modules/users/components/UsersTable.tsx
index 407aad69f..d1e020973 100644
--- a/pnpm-monorepo/apps/app/src/modules/users/components/UsersTable.tsx
+++ b/pnpm-monorepo/apps/app/src/modules/users/components/UsersTable.tsx
@@ -124,6 +124,7 @@ export const UsersTable = ({
|
{user.bannedAt && (
Gesperrt
diff --git a/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiCollabStatusDot.tsx b/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiCollabStatusDot.tsx
index 08797b9f3..74494df1b 100644
--- a/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiCollabStatusDot.tsx
+++ b/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiCollabStatusDot.tsx
@@ -76,6 +76,7 @@ export const WikiCollabStatusDot = ({ className, status, users }: Props) => {
return (
diff --git a/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiGutter.tsx b/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiGutter.tsx
index 6de590240..38e1d6b36 100644
--- a/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiGutter.tsx
+++ b/pnpm-monorepo/apps/app/src/modules/wiki/components/WikiGutter.tsx
@@ -209,6 +209,7 @@ export const WikiGutter = ({
onMouseLeave={() => setControlsHovered(false)}
>
{
return (
({
return (
{
if (!nextOpen) onDismiss();
diff --git a/pnpm-monorepo/apps/app/src/modules/wiki/components/toolbar/ToolbarPopover.tsx b/pnpm-monorepo/apps/app/src/modules/wiki/components/toolbar/ToolbarPopover.tsx
index f4d34e9d3..654f06fec 100644
--- a/pnpm-monorepo/apps/app/src/modules/wiki/components/toolbar/ToolbarPopover.tsx
+++ b/pnpm-monorepo/apps/app/src/modules/wiki/components/toolbar/ToolbarPopover.tsx
@@ -21,6 +21,7 @@ export const ToolbarPopover = ({
}: Props) => {
return (
({
},
async onRequest(data) {
+ /**
+ * Readiness probe. An open port only means Node bound the socket —
+ * this answers once the server is actually serving, which is what an
+ * orchestrator (and the Playwright stack) needs to wait for.
+ */
+ if (data.request.method === "GET" && data.request.url === "/health") {
+ respondJson(data.response, 200, { status: "ok" });
+ throw null;
+ }
+
if (data.request.method === "POST" && data.request.url === "/replace") {
try {
await handleReplaceRequest(data.request, data.response);
@@ -594,3 +604,29 @@ const server = new Server({
await server.listen();
console.log(`[collab] Listening on port ${env.PORT}`);
+
+/**
+ * Edits are persisted on a debounce, so an abrupt exit drops up to
+ * `maxDebounce` worth of them. Hocuspocus' destroy() closes the
+ * connections and awaits the store hooks for every open document, so a
+ * deploy or a container restart lands after the writes instead of on top
+ * of them.
+ */
+let isShuttingDown = false;
+
+const shutDown = async (signal: NodeJS.Signals) => {
+ if (isShuttingDown) return;
+ isShuttingDown = true;
+ console.log(`[collab] ${signal} received, flushing open documents`);
+
+ try {
+ await server.destroy();
+ console.log("[collab] Shutdown complete");
+ } catch (error) {
+ console.error("[collab] Shutdown failed", error);
+ process.exitCode = 1;
+ }
+};
+
+process.on("SIGTERM", (signal) => void shutDown(signal));
+process.on("SIGINT", (signal) => void shutDown(signal));
diff --git a/pnpm-monorepo/apps/playwright/fixtures/interactions.ts b/pnpm-monorepo/apps/playwright/fixtures/interactions.ts
index 4c5e45755..be0d01ec5 100644
--- a/pnpm-monorepo/apps/playwright/fixtures/interactions.ts
+++ b/pnpm-monorepo/apps/playwright/fixtures/interactions.ts
@@ -7,12 +7,12 @@ import { expect, type Locator, type Page } from "@playwright/test";
export const ACTION_FEEDBACK_TIMEOUT = 15_000;
/**
- * The app's Modal (Base UI dialog) renders in a portal with role="dialog",
- * but its heading is not always a heading element — so modals are located
- * by their heading text instead of an accessible name.
+ * The app's Modal (Base UI dialog) renders in a portal with role="dialog"
+ * and takes its accessible name from the heading it is given. Popovers use
+ * the same role but carry their own name, so this never matches one.
*/
export const modal = (page: Page, heading: string | RegExp) =>
- page.getByRole("dialog").filter({ hasText: heading });
+ page.getByRole("dialog", { name: heading });
/**
* Interactions landing before React hydrates are swallowed: clicks fall on
@@ -97,21 +97,11 @@ export const tabUntilFocused = (page: Page, target: Locator) =>
}).toPass({ timeout: HYDRATION_TIMEOUT });
/**
- * EditableField wraps its display button and its save form in an inline
- * ; Playwright's hit-target validation misattributes clicks on them
- * to that span (" intercepts pointer events") although real clicks
- * do reach the button — force skips only that validation. The reaction
- * check keeps the pre-hydration retry honest.
+ * Submits the open inline editor (EditableField). Only one can be open at a
+ * time, so the icon-only save button is unambiguous without scoping.
*/
-export const openInlineEditor = (editButton: Locator, editorInput: Locator) =>
- expect(async () => {
- await editButton.click({ force: true, timeout: REACTION_TIMEOUT });
- await expect(editorInput).toBeVisible({ timeout: REACTION_TIMEOUT });
- }).toPass({ timeout: HYDRATION_TIMEOUT });
-
-/** See openInlineEditor — the save button sits in the same inline wrapper. */
export const saveInlineEditor = (page: Page) =>
- page.locator('button[title="Speichern"]').click({ force: true });
+ page.locator('button[title="Speichern"]').click();
/**
* Proves the page has hydrated by opening and closing the notification
@@ -122,7 +112,7 @@ export const saveInlineEditor = (page: Page) =>
*/
export const waitForAppShellHydration = async (page: Page) => {
const bellButton = page.getByRole("button", { name: "Benachrichtigungen" });
- const popover = page.getByRole("dialog");
+ const popover = page.getByRole("dialog", { name: "Benachrichtigungen" });
await clickUntilVisible(bellButton, popover);
await page.keyboard.press("Escape");
await expect(popover).not.toBeVisible();
diff --git a/pnpm-monorepo/apps/playwright/fixtures/test.ts b/pnpm-monorepo/apps/playwright/fixtures/test.ts
index 23121f75d..afdde2d4e 100644
--- a/pnpm-monorepo/apps/playwright/fixtures/test.ts
+++ b/pnpm-monorepo/apps/playwright/fixtures/test.ts
@@ -128,7 +128,9 @@ export const test = base.extend({
COLLAB_JWT_SECRET: collabJwtSecret,
})
.withExposedPorts(collabPort)
- .withWaitStrategy(Wait.forListeningPorts())
+ // An open port only means the socket is bound; /health answers once
+ // the server is actually serving requests.
+ .withWaitStrategy(Wait.forHttp("/health", collabPort))
.start();
const appPort = await getFreePort();
diff --git a/pnpm-monorepo/apps/playwright/setup/stack.ts b/pnpm-monorepo/apps/playwright/setup/stack.ts
index 55a4b1684..bb8152f8e 100644
--- a/pnpm-monorepo/apps/playwright/setup/stack.ts
+++ b/pnpm-monorepo/apps/playwright/setup/stack.ts
@@ -91,6 +91,9 @@ export const unleashEnvironment = (unleashPort: number) =>
({
UNLEASH_SERVER_API_URL: `http://localhost:${unleashPort}/api`,
UNLEASH_SERVER_API_TOKEN: unleashBackendToken,
+ // Production caches the flag definitions for 30s; a toggle in a test
+ // must not wait that out (see tests/unleash.spec.ts).
+ UNLEASH_REVALIDATE_SECONDS: "1",
}) as const;
/**
diff --git a/pnpm-monorepo/apps/playwright/tests/apps-favorites.spec.ts b/pnpm-monorepo/apps/playwright/tests/apps-favorites.spec.ts
index b59a76dd3..ec5fbf360 100644
--- a/pnpm-monorepo/apps/playwright/tests/apps-favorites.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/apps-favorites.spec.ts
@@ -1,23 +1,20 @@
import type { Page } from "@playwright/test";
import { createCitizen } from "../fixtures/factories";
+import { clickUntilVisible } from "../fixtures/interactions";
import { expect, test } from "../fixtures/test";
/**
- * Base UI renders the popup with `role="dialog"`. Scoping to it keeps the
+ * Base UI renders the popup with `role="dialog"`; its name keeps the
* assertions away from the mobile flyout, which lists the same apps.
*/
-const appsPopover = (page: Page) => page.getByRole("dialog");
+const appsPopover = (page: Page) =>
+ page.getByRole("dialog", { name: "Apps", exact: true });
-/**
- * The popover opens on hover — clicking the trigger would race the hover
- * delay and could toggle it straight back closed.
- */
-const openAppsPopover = async (page: Page) => {
- await page.getByRole("button", { name: "Apps" }).hover();
- await expect(
+const openAppsPopover = (page: Page) =>
+ clickUntilVisible(
+ page.getByRole("button", { name: "Apps" }),
appsPopover(page).getByText("Featured", { exact: true }),
- ).toBeVisible({ timeout: 15_000 });
-};
+ );
const favoritesHeading = (page: Page) =>
appsPopover(page).getByText("Favoriten", { exact: true });
diff --git a/pnpm-monorepo/apps/playwright/tests/collab-replace.spec.ts b/pnpm-monorepo/apps/playwright/tests/collab-replace.spec.ts
index e1cd36bae..b79271496 100644
--- a/pnpm-monorepo/apps/playwright/tests/collab-replace.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/collab-replace.spec.ts
@@ -47,40 +47,38 @@ test("a programmatic /replace creates suppressed links only", async ({
});
/**
- * Node's fetch instead of Playwright's request fixture (whose client
- * trips over Hocuspocus' plain-HTTP handling), retried in case the
- * freshly started collab container isn't serving HTTP yet. A genuinely
- * broken endpoint still fails every attempt.
+ * Node's fetch instead of Playwright's request fixture, whose client
+ * trips over Hocuspocus' plain-HTTP handling. The container is waited on
+ * with its /health route, so the endpoint is serving by now.
*/
- await expect(async () => {
- const response = await fetch(`${collabHttpUrl}/replace`, {
- method: "POST",
- headers: {
- authorization: `Bearer ${signReplaceToken(wikiPage.id, author.entity.id)}`,
- "content-type": "application/json",
- },
- body: JSON.stringify({
- content: {
- type: "doc",
- content: [
- {
- type: "paragraph",
- content: [
- {
- type: "wikiCitizenMention",
- attrs: {
- citizenId: mentioned.entity.id,
- handle: "Zielperson",
- },
+ const response = await fetch(`${collabHttpUrl}/replace`, {
+ method: "POST",
+ headers: {
+ authorization: `Bearer ${signReplaceToken(wikiPage.id, author.entity.id)}`,
+ "content-type": "application/json",
+ },
+ body: JSON.stringify({
+ content: {
+ type: "doc",
+ content: [
+ {
+ type: "paragraph",
+ content: [
+ {
+ type: "wikiCitizenMention",
+ attrs: {
+ citizenId: mentioned.entity.id,
+ handle: "Zielperson",
},
- ],
- },
- ],
- },
- }),
- });
- expect(response.ok).toBe(true);
- }).toPass({ timeout: 15_000 });
+ },
+ ],
+ },
+ ],
+ },
+ }),
+ signal: AbortSignal.timeout(15_000),
+ });
+ expect(response.ok).toBe(true);
await expect
.poll(
diff --git a/pnpm-monorepo/apps/playwright/tests/fleet.spec.ts b/pnpm-monorepo/apps/playwright/tests/fleet.spec.ts
index 41612e6a4..63b862a1d 100644
--- a/pnpm-monorepo/apps/playwright/tests/fleet.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/fleet.spec.ts
@@ -11,7 +11,6 @@ import {
clickUntilVisible,
fillUntilUrl,
modal,
- openInlineEditor,
saveInlineEditor,
} from "../fixtures/interactions";
import { expect, test } from "../fixtures/test";
@@ -150,7 +149,7 @@ test("my ships can be added, renamed and deleted with consistent org counts", as
// Rename
await page.goto("/app/fleet/my-ships");
const nameInput = page.locator('input[name="name"]');
- await openInlineEditor(
+ await clickUntilVisible(
shipRow.locator('button[title="Klicken, um zu bearbeiten"]'),
nameInput,
);
@@ -223,7 +222,7 @@ test("manufacturers and series can be managed through the REST-backed settings",
`/app/fleet/settings/manufacturer/${manufacturer!.id}`,
);
const nameInput = page.locator('input[name="name"]');
- await openInlineEditor(
+ await clickUntilVisible(
page.locator('button[title="Klicken, um zu bearbeiten"]'),
nameInput,
);
diff --git a/pnpm-monorepo/apps/playwright/tests/iam.spec.ts b/pnpm-monorepo/apps/playwright/tests/iam.spec.ts
index e22b34b55..1bd98f99f 100644
--- a/pnpm-monorepo/apps/playwright/tests/iam.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/iam.spec.ts
@@ -1,23 +1,7 @@
-import type { Page } from "@playwright/test";
import { createCitizen } from "../fixtures/factories";
+import { ACTION_FEEDBACK_TIMEOUT, modal } from "../fixtures/interactions";
import { expect, test } from "../fixtures/test";
-/**
- * The app's Modal renders as a portal without dialog semantics (the Base UI
- * rewrite is planned), so modals are located by their heading instead of
- * getByRole("dialog").
- */
-const modal = (page: Page, heading: string) =>
- page
- .locator("body > div")
- .filter({ has: page.getByRole("heading", { name: heading }) });
-
-/**
- * Mutations run as server actions against a worker stack under full-suite
- * load — their success feedback regularly needs more than the 5s default.
- */
-const ACTION_FEEDBACK_TIMEOUT = 15_000;
-
test("a role created and assigned through the UI grants its permission", async ({
page,
prisma,
diff --git a/pnpm-monorepo/apps/playwright/tests/notification-center.spec.ts b/pnpm-monorepo/apps/playwright/tests/notification-center.spec.ts
index 7e1a4b1ea..7e48efd97 100644
--- a/pnpm-monorepo/apps/playwright/tests/notification-center.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/notification-center.spec.ts
@@ -8,9 +8,10 @@ const bellButton = (page: Page) =>
/**
* The notification center is mounted twice (top bar popover and the hidden
* mobile flyout), so desktop assertions are scoped to the open popover —
- * Base UI renders its popup with `role="dialog"`.
+ * Base UI renders its popup with `role="dialog"`, named after its trigger.
*/
-const popover = (page: Page) => page.getByRole("dialog");
+const popover = (page: Page) =>
+ page.getByRole("dialog", { name: "Benachrichtigungen" });
const openNotificationCenter = async (page: Page) => {
await bellButton(page).click();
diff --git a/pnpm-monorepo/apps/playwright/tests/notification-settings.spec.ts b/pnpm-monorepo/apps/playwright/tests/notification-settings.spec.ts
index 050280334..81c4cae3e 100644
--- a/pnpm-monorepo/apps/playwright/tests/notification-settings.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/notification-settings.spec.ts
@@ -8,9 +8,10 @@ import {
import { expect, test } from "../fixtures/test";
/**
- * The checkbox inputs are sr-only without an accessible name — they are
- * located by their form field name (ONSITE_ / WEB_PUSH_) and
- * toggled through their wrapping label.
+ * The checkbox input itself is sr-only — the visible control is the box its
+ * wrapping label draws, so toggling goes through the label the way a click
+ * does. State is read off the input, located by its form field name
+ * (ONSITE_ / WEB_PUSH_) so the cases stay written in ids.
*/
const browserCheckbox = (page: Page, notificationType: string) =>
page.locator(`input[name="WEB_PUSH_${notificationType}"]`);
@@ -36,6 +37,15 @@ test("browser notifications are enabled by default", async ({
await expect(browserCheckbox(page, "event_created")).toBeChecked();
await expect(browserCheckbox(page, "wiki_page_reported")).toBeChecked();
+ // Each box names its channel and its notification type; the visible
+ // Ja/Nein text is the state, never the name
+ await expect(
+ page.getByRole("checkbox", { name: "Browser: Neues Event" }),
+ ).toBeChecked();
+ await expect(
+ page.getByRole("checkbox", { name: "On-site: Neues Event" }),
+ ).toBeDisabled();
+
const settingsCount = await prisma.notificationSetting.count({
where: { citizenId: citizen.entity.id },
});
diff --git a/pnpm-monorepo/apps/playwright/tests/spynet.spec.ts b/pnpm-monorepo/apps/playwright/tests/spynet.spec.ts
index 0b780648d..8b934eb5c 100644
--- a/pnpm-monorepo/apps/playwright/tests/spynet.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/spynet.spec.ts
@@ -205,29 +205,28 @@ const exerciseSettingsRecordCrud = async (
timeout: ACTION_FEEDBACK_TIMEOUT,
});
- /**
- * The record's actions menu is an icon-only popover trigger, and a late
- * router.refresh() re-render can unmount the open popover — so opening
- * an action retries the whole open-popover-then-click sequence.
- */
const actionsTrigger = (record: string) =>
tile
.locator("li, tr, article, div")
.filter({ hasText: record })
- .getByRole("button")
+ .getByRole("button", { name: "Aktionen" })
.last();
- const openRowAction = (
+ /**
+ * The menu stays open behind the modal it opened, so by the delete step it
+ * is already showing — opening it again would toggle it shut and detach
+ * the button before the click lands.
+ */
+ const openRowAction = async (
record: string,
actionLabel: string,
reaction: Locator,
- ) =>
- expect(async () => {
- const actionButton = page.getByRole("button", { name: actionLabel });
- if (!(await actionButton.isVisible()))
- await actionsTrigger(record).click({ timeout: 1_000 });
- await actionButton.click({ timeout: 1_000 });
- await expect(reaction).toBeVisible({ timeout: 1_000 });
- }).toPass({ timeout: ACTION_FEEDBACK_TIMEOUT });
+ ) => {
+ const actionButton = page.getByRole("button", { name: actionLabel });
+ if (!(await actionButton.isVisible()))
+ await clickUntilVisible(actionsTrigger(record), actionButton);
+ await actionButton.click();
+ await expect(reaction).toBeVisible({ timeout: ACTION_FEEDBACK_TIMEOUT });
+ };
// Update
const updateModal = modal(page, "Bearbeiten");
diff --git a/pnpm-monorepo/apps/playwright/tests/tasks.spec.ts b/pnpm-monorepo/apps/playwright/tests/tasks.spec.ts
index 549333dbf..a5628b769 100644
--- a/pnpm-monorepo/apps/playwright/tests/tasks.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/tasks.spec.ts
@@ -6,7 +6,6 @@ import {
ACTION_FEEDBACK_TIMEOUT,
clickUntilVisible,
modal,
- openInlineEditor,
saveInlineEditor,
} from "../fixtures/interactions";
import { expect, test } from "../fixtures/test";
@@ -80,7 +79,7 @@ test("a task can be created and two of its fields edited through the shared fact
// Two different fields of the shared field-update factory: title …
await page.getByRole("link", { name: /Erztransport eskortieren/ }).click();
const titleInput = page.locator('input[name="title"]');
- await openInlineEditor(editButtons(page).first(), titleInput);
+ await clickUntilVisible(editButtons(page).first(), titleInput);
await titleInput.fill("Titan-Erz eskortieren");
await saveInlineEditor(page);
await expect(editButtons(page).first()).toContainText(
@@ -91,7 +90,7 @@ test("a task can be created and two of its fields edited through the shared fact
// … and description
const descriptionSection = tileSection(page, "Beschreibung");
const descriptionInput = page.locator('textarea[name="description"]');
- await openInlineEditor(editButtons(descriptionSection), descriptionInput);
+ await clickUntilVisible(editButtons(descriptionSection), descriptionInput);
await descriptionInput.fill("Begleitschutz von Lorville nach Everus Harbor.");
await saveInlineEditor(page);
await expect(descriptionSection).toContainText(
diff --git a/pnpm-monorepo/apps/playwright/tests/unleash.spec.ts b/pnpm-monorepo/apps/playwright/tests/unleash.spec.ts
index 414504593..cf2fd6631 100644
--- a/pnpm-monorepo/apps/playwright/tests/unleash.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/unleash.spec.ts
@@ -4,16 +4,16 @@ import { expect, test } from "../fixtures/test";
import { setUnleashFlag, UNLEASH_FLAG } from "../fixtures/unleash";
/**
- * Flag changes reach the app only after its 30 second definitions cache
- * expires (see the app's getUnleashFlag), so every state assertion polls
- * with a timeout well above that. Both flag states are asserted, so a
- * timeout here means the app did not pick up the change from the stack's
- * Unleash container — not that the default kicked in.
+ * The stack runs the app with a one second flag cache
+ * (UNLEASH_REVALIDATE_SECONDS, see setup/stack.ts), so a toggle shows up
+ * after roughly one navigation. The headroom is for suite load. Both flag
+ * states are asserted, so a timeout here means the app did not pick up the
+ * change from the stack's Unleash container — not that the default kicked in.
*/
-const FLAG_PROPAGATION_TIMEOUT = 90_000;
-const FLAG_PROPAGATION_INTERVALS = [2_000];
+const FLAG_PROPAGATION_TIMEOUT = 30_000;
+const FLAG_PROPAGATION_INTERVALS = [1_000];
/** Two polled flag states plus navigations per test */
-const FLAG_TEST_TIMEOUT = 300_000;
+const FLAG_TEST_TIMEOUT = 120_000;
/**
* Navigates and reports whether the element shows up. The wait covers
diff --git a/pnpm-monorepo/apps/playwright/tests/variant-wiki.spec.ts b/pnpm-monorepo/apps/playwright/tests/variant-wiki.spec.ts
index d5d8e70e1..95a774d9e 100644
--- a/pnpm-monorepo/apps/playwright/tests/variant-wiki.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/variant-wiki.spec.ts
@@ -344,13 +344,15 @@ test("the update variant modal links a wiki page", async ({
await page.goto(
`/app/fleet/settings/manufacturer/${manufacturer.id}/series/${series.id}`,
);
- // The row's unnamed ellipsis button opens the actions popover
const editButton = page.getByRole("button", {
name: "Bearbeiten",
exact: true,
});
await clickUntilVisible(
- page.getByRole("row").filter({ hasText: variant.name }).getByRole("button"),
+ page
+ .getByRole("row")
+ .filter({ hasText: variant.name })
+ .getByRole("button", { name: "Aktionen" }),
editButton,
);
diff --git a/pnpm-monorepo/apps/playwright/tests/wiki-editing.spec.ts b/pnpm-monorepo/apps/playwright/tests/wiki-editing.spec.ts
index 17a9a4bd9..bf6350beb 100644
--- a/pnpm-monorepo/apps/playwright/tests/wiki-editing.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/wiki-editing.spec.ts
@@ -70,7 +70,7 @@ test("the slash palette opens and inserts a block", async ({
// the empty page starts with an empty paragraph, so this is a block start
await page.keyboard.type("/");
- const palette = page.getByRole("dialog");
+ const palette = page.getByRole("dialog", { name: "Vorschläge" });
await expect(
palette.locator("[data-suggestion-index]:visible").first(),
).toBeVisible();
diff --git a/pnpm-monorepo/apps/playwright/tests/wiki-mentions.spec.ts b/pnpm-monorepo/apps/playwright/tests/wiki-mentions.spec.ts
index 06ae68c89..3d45724da 100644
--- a/pnpm-monorepo/apps/playwright/tests/wiki-mentions.spec.ts
+++ b/pnpm-monorepo/apps/playwright/tests/wiki-mentions.spec.ts
@@ -13,7 +13,7 @@ const insertMention = async (
handle: string,
) => {
await page.keyboard.type(`@${handle.slice(0, 5)}`);
- const menu = page.getByRole("dialog");
+ const menu = page.getByRole("dialog", { name: "Vorschläge" });
await menu
.locator("[data-suggestion-index]:visible", { hasText: handle })
.first()
|