# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: sources.spec.ts >> Board access >> preview boards cannot import files through drag and drop
- Location: e2e/sources.spec.ts:265:7

# Error details

```
Error: expect(locator).toHaveCount(expected) failed

Locator:  locator('.react-flow__node-text')
Expected: 0
Received: 1
Timeout:  10000ms

Call log:
  - Expect "toHaveCount" locator('.react-flow__node-text') with timeout 10000ms
  - waiting for locator('.react-flow__node-text')
    23 × locator resolved to 1 element
       - unexpected value "1"

```

# Page snapshot

```yaml
- generic [active] [ref=f1e1]:
  - main [ref=f1e3]:
    - application [ref=f1e7]:
      - group [ref=f1e10]:
        - generic [ref=f1e11]:
          - generic [ref=f1e21] [cursor=pointer]:
            - generic [ref=f1e24]: Text
            - generic [ref=f1e25]:
              - button [ref=f1e26]
              - button "Open menu" [ref=f1e28]
          - generic [ref=f1e31]:
            - textbox [ref=f1e36]:
              - paragraph [ref=f1e37]: This preview must remain unchanged
            - button [ref=f1e40] [cursor=pointer]
          - generic [ref=f1e44] [cursor=pointer]
          - generic [ref=f1e45] [cursor=pointer]
  - region "Notifications (F8)":
    - list
  - button "Open Next.js Dev Tools" [ref=f1e52] [cursor=pointer]
  - alert [ref=f1e56]
```

# Test source

```ts
  182 |     await expect(page.locator(".react-flow__node-text")).toContainText(
  183 |       "An imported research observation"
  184 |     );
  185 |   });
  186 |   test("oversized dropped text files are rejected before any source creation", async ({
  187 |     page,
  188 |     backend,
  189 |   }) => {
  190 |     const transfer = await page.evaluateHandle(() => {
  191 |       const data = new DataTransfer();
  192 |       data.items.add(
  193 |         new File(["x".repeat(1024 * 1024 + 1)], "oversized.txt", { type: "text/plain" })
  194 |       );
  195 |       return data;
  196 |     });
  197 |     await page
  198 |       .locator(".react-flow__pane")
  199 |       .dispatchEvent("drop", { dataTransfer: transfer, clientX: 650, clientY: 450 });
  200 |     await settleFileDrop(page, transfer);
  201 |     await expect(page.getByText("File too large", { exact: true })).toBeVisible();
  202 |     await expect(page.locator(".react-flow__node-text")).toHaveCount(0);
  203 |     expect(
  204 |       (await backend.requests()).filter((r) => r.method === "POST" && r.path === "/source/text")
  205 |     ).toHaveLength(0);
  206 |   });
  207 | });
  208 | 
  209 | test.describe("Board access", () => {
  210 |   test("viewer can read content but toolbar and keyboard cannot mutate it", async ({
  211 |     page,
  212 |     backend,
  213 |   }) => {
  214 |     await page.goto("/board/3");
  215 |     await expect(page.locator(".react-flow")).toBeVisible();
  216 |     for (const name of ["Add Text", "Sticky Note", "AI Assistant", "Add Website", "Create group"]) {
  217 |       await expect(
  218 |         page.locator(".flow-toolbar").getByRole("button", { name, exact: true })
  219 |       ).toBeDisabled();
  220 |     }
  221 |     await expect(page.getByRole("button", { name: "Share board", exact: true })).toHaveCount(0);
  222 |     await page.locator(".react-flow__pane").click({ position: { x: 1100, y: 500 } });
  223 |     await page.keyboard.press("t");
  224 |     await page.keyboard.press("Control+a");
  225 |     await page.keyboard.press("Delete");
  226 |     expect(
  227 |       (await backend.requests()).filter(
  228 |         (r) =>
  229 |           (r.method === "POST" &&
  230 |             ["/source/text", "/chat-node/3", "/board/3/items/batch/delete"].includes(r.path)) ||
  231 |           r.method === "DELETE"
  232 |       )
  233 |     ).toHaveLength(0);
  234 |   });
  235 | 
  236 |   test("viewer cannot import files by dropping them onto a read-only board", async ({
  237 |     page,
  238 |     backend,
  239 |   }) => {
  240 |     await page.goto("/board/3");
  241 |     await expect(
  242 |       page.locator(".flow-toolbar").getByRole("button", { name: "Add Text", exact: true })
  243 |     ).toBeDisabled();
  244 |     const transfer = await page.evaluateHandle(() => {
  245 |       const data = new DataTransfer();
  246 |       data.items.add(
  247 |         new File(["This must remain read-only"], "viewer.txt", { type: "text/plain" })
  248 |       );
  249 |       return data;
  250 |     });
  251 |     await page
  252 |       .locator(".react-flow__pane")
  253 |       .dispatchEvent("drop", { dataTransfer: transfer, clientX: 650, clientY: 450 });
  254 |     await settleFileDrop(page, transfer);
  255 |     await expect(page.locator(".react-flow__node-text")).toHaveCount(0);
  256 |     await page.reload();
  257 |     await expect(
  258 |       page.locator(".flow-toolbar").getByRole("button", { name: "Add Text", exact: true })
  259 |     ).toBeDisabled();
  260 |     expect(
  261 |       (await backend.requests()).filter((r) => r.method === "POST" && r.path === "/source/text")
  262 |     ).toHaveLength(0);
  263 |   });
  264 | 
  265 |   test("preview boards cannot import files through drag and drop", async ({ page, backend }) => {
  266 |     await page.goto("/board/1?preview=1");
  267 |     await expect(page.locator(".react-flow")).toBeVisible();
  268 |     await expect(
  269 |       page.locator(".flow-toolbar").getByRole("button", { name: "Add Text", exact: true })
  270 |     ).toHaveCount(0);
  271 |     const transfer = await page.evaluateHandle(() => {
  272 |       const data = new DataTransfer();
  273 |       data.items.add(
  274 |         new File(["This preview must remain unchanged"], "preview.txt", { type: "text/plain" })
  275 |       );
  276 |       return data;
  277 |     });
  278 |     await page
  279 |       .locator(".react-flow__pane")
  280 |       .dispatchEvent("drop", { dataTransfer: transfer, clientX: 650, clientY: 450 });
  281 |     await settleFileDrop(page, transfer);
> 282 |     await expect(page.locator(".react-flow__node-text")).toHaveCount(0);
      |                                                          ^ Error: expect(locator).toHaveCount(expected) failed
  283 |     await page.reload();
  284 |     await expect(page.locator(".react-flow")).toBeVisible();
  285 |     expect(
  286 |       (await backend.requests()).filter((r) => r.method === "POST" && r.path === "/source/text")
  287 |     ).toHaveLength(0);
  288 |   });
  289 | 
  290 |   test("preview text content is readable but cannot be edited", async ({ page, backend }) => {
  291 |     await backend.configure({
  292 |       sources: [
  293 |         {
  294 |           id: 11,
  295 |           boardId: 1,
  296 |           sourceId: "11",
  297 |           positionX: 0,
  298 |           positionY: 0,
  299 |           styles: JSON.stringify({ width: 600, height: 400, mode: "full" }),
  300 |           updateId: 0,
  301 |           source: {
  302 |             id: 11,
  303 |             type: "text",
  304 |             status: "success",
  305 |             title: "Preview research",
  306 |             text: "<p>A read-only preview observation</p>",
  307 |           },
  308 |         },
  309 |       ],
  310 |     });
  311 |     await page.goto("/board/1?preview=1");
  312 |     const node = page.locator('.react-flow__node[data-id="source-11"]');
  313 |     await expect(node).toContainText("A read-only preview observation");
  314 |     await expect(node.locator(".tiptap")).toHaveAttribute("contenteditable", "false");
  315 |     expect(
  316 |       (await backend.requests()).filter((r) => r.method === "PATCH" && r.path === "/source/11/text")
  317 |     ).toHaveLength(0);
  318 |   });
  319 | 
  320 |   test("missing board shows an actionable access error instead of an endless spinner", async ({
  321 |     page,
  322 |   }) => {
  323 |     await page.goto("/board/99999");
  324 |     await expect(page.getByText("Failed to load board", { exact: false })).toBeVisible();
  325 |     await expect(page.getByRole("button", { name: "Go to home", exact: false })).toBeVisible();
  326 |     await expect(page.locator(".react-flow")).toHaveCount(0);
  327 |   });
  328 | });
  329 | 
```