e2e: fix flaky tests by adding page readiness chaeck
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled

This commit is contained in:
Nicolas Meienberger 2026-02-17 20:13:19 +01:00
parent 85e2272a5c
commit 289832d58b
5 changed files with 38 additions and 12 deletions

View file

@ -4,6 +4,7 @@ import { apiClientMiddleware } from "~/middleware/api-client";
import type { QueryClient } from "@tanstack/react-query";
import { Toaster } from "~/client/components/ui/sonner";
import { useServerEvents } from "~/client/hooks/use-server-events";
import { useEffect } from "react";
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
server: {
@ -34,6 +35,12 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
function RootLayout() {
useServerEvents();
useEffect(() => {
document.body.setAttribute("data-app-ready", "true");
return () => {
document.body.removeAttribute("data-app-ready");
};
}, []);
return (
<html lang="en">

View file

@ -2,6 +2,7 @@ import fs from "fs";
import { test, expect } from "@playwright/test";
import { resetDatabase } from "./helpers/db";
import path from "node:path";
import { gotoAndWaitForAppReady } from "./helpers/page";
const authFile = path.join(process.cwd(), "./playwright/.auth/user.json");
@ -15,7 +16,7 @@ test.beforeAll(async () => {
});
test("should redirect to onboarding", async ({ page }) => {
await page.goto("/");
await gotoAndWaitForAppReady(page, "/");
await page.waitForURL(/onboarding/);
@ -23,7 +24,7 @@ test("should redirect to onboarding", async ({ page }) => {
});
test("user can register a new account", async ({ page }) => {
await page.goto("/onboarding");
await gotoAndWaitForAppReady(page, "/onboarding");
await page.getByRole("textbox", { name: "Email" }).click();
await page.getByRole("textbox", { name: "Email" }).fill("test@test.com");
@ -39,7 +40,7 @@ test("user can register a new account", async ({ page }) => {
});
test("user can download recovery key", async ({ page }) => {
await page.goto("/login");
await gotoAndWaitForAppReady(page, "/login");
await page.getByRole("textbox", { name: "Username" }).fill("test");
await page.getByRole("textbox", { name: "Password" }).fill("password");
@ -69,7 +70,7 @@ test("user can download recovery key", async ({ page }) => {
});
test("can't create another admin user after initial setup", async ({ page }) => {
await page.goto("/onboarding");
await gotoAndWaitForAppReady(page, "/onboarding");
await page.getByRole("textbox", { name: "Email" }).click();
await page.getByRole("textbox", { name: "Email" }).fill("test@test.com");
@ -85,7 +86,7 @@ test("can't create another admin user after initial setup", async ({ page }) =>
});
test("can login after initial setup", async ({ page }) => {
await page.goto("/login");
await gotoAndWaitForAppReady(page, "/login");
await page.getByRole("textbox", { name: "Username" }).fill("test");
await page.getByRole("textbox", { name: "Password" }).fill("password");

View file

@ -1,6 +1,7 @@
import { expect, test } from "@playwright/test";
import path from "node:path";
import fs from "node:fs";
import { gotoAndWaitForAppReady } from "./helpers/page";
test.describe.configure({ mode: "serial" });
@ -16,7 +17,7 @@ test.beforeAll(() => {
});
test("can backup & restore a file", async ({ page }) => {
await page.goto("/");
await gotoAndWaitForAppReady(page, "/");
await expect(page).toHaveURL("/volumes");
// 0. Create a test file in /test-data
@ -75,17 +76,24 @@ test("can backup & restore a file", async ({ page }) => {
});
test("deleting a volume cascades and removes its backup schedule", async ({ page }) => {
await page.goto("/backups");
await gotoAndWaitForAppReady(page, "/backups");
await page.getByText("Test Backup", { exact: true }).first().click();
await expect(page.getByRole("link", { name: "Test Volume" })).toBeVisible();
await page.getByRole("link", { name: "Test Volume" }).click();
const volumeLink = page.locator("main").getByRole("link", { name: "Test Volume", exact: true }).first();
await expect(volumeLink).toBeVisible();
await volumeLink.click();
await expect(page).toHaveURL(/\/volumes\/[^/?#]+/);
await expect(page.getByText("Volume Configuration", { exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Delete" })).toBeVisible();
await page.getByRole("button", { name: "Delete" }).click();
await expect(async () => {
await page.getByRole("button", { name: "Delete" }).click();
await expect(page.getByRole("heading", { name: "Delete volume?" })).toBeVisible();
}).toPass({ timeout: 10000 });
await expect(page.getByText("All backup schedules associated with this volume will also be removed.")).toBeVisible();
await page.getByRole("button", { name: "Delete volume" }).click();
await expect(page.getByText("Volume deleted successfully")).toBeVisible();
await page.goto("/backups");
await gotoAndWaitForAppReady(page, "/backups");
await expect(page.getByText("Test Backup", { exact: true })).toHaveCount(0);
});

10
e2e/helpers/page.ts Normal file
View file

@ -0,0 +1,10 @@
import type { Page } from "@playwright/test";
export const waitForAppReady = async (page: Page) => {
await page.waitForFunction(() => document.body?.getAttribute("data-app-ready") === "true");
};
export const gotoAndWaitForAppReady = async (page: Page, url: string) => {
await page.goto(url, { waitUntil: "domcontentloaded" });
await waitForAppReady(page);
};

View file

@ -11,7 +11,7 @@ export default defineConfig({
use: {
baseURL: `http://${process.env.SERVER_IP}:4096`,
video: "retain-on-failure",
trace: "on-first-retry",
trace: "retain-on-failure",
},
projects: [
{