feat(e2e): add playright config and createBrain e2e test (#1177)

* chore: add playright config

* feat: add playright first examples

* feat: add 'test-e2e command'

* feat: add createBrain E2E test
This commit is contained in:
Mamadou DICKO 2023-09-18 10:23:56 +02:00 committed by GitHub
parent 143d32ddf1
commit 7dd404935b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 378 additions and 353 deletions

View File

@ -6,3 +6,6 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=<change-me>
NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY=<ignore-me-or-change-me>
NEXT_PUBLIC_JUNE_API_KEY=<ignore-me-or-change-me>
NEXT_PUBLIC_GROWTHBOOK_URL=<ignore-me-or-change-me>
NEXT_PUBLIC_E2E_URL=http://localhost:3003
NEXT_PUBLIC_E2E_EMAIL=<ignore-me-or-change-me>
NEXT_PUBLIC_E2E_PASSWORD=<ignore-me-or-change-me>

3
frontend/.gitignore vendored
View File

@ -36,3 +36,6 @@ next-env.d.ts
# Sentry Auth Token
.sentryclirc
/test-results/
/playwright-report/
/playwright/.cache/

View File

@ -8,22 +8,24 @@ import { Divider } from "@/lib/components/ui/Divider";
import Field from "@/lib/components/ui/Field";
import PageHeading from "@/lib/components/ui/PageHeading";
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import { GoogleLoginButton } from "./components/GoogleLogin";
import { MagicLinkLogin } from "./components/MagicLinkLogin";
import { PasswordForgotten } from "./components/PasswordForgotten";
import { useLogin } from "./hooks/useLogin";
import { useTranslation } from "react-i18next";
import { Suspense } from "react";
function Main() {
const { handleLogin, setEmail, setPassword, email, isPending, password } =
useLogin();
const { t } = useTranslation(["translation","login"]);
const { t } = useTranslation(["translation", "login"]);
return (
<main>
<section className="w-full min-h-[80vh] h-full outline-none flex flex-col gap-5 items-center justify-center p-6">
<PageHeading title={t("title",{ ns: 'login' })} subtitle={t("subtitle",{ ns: 'login' })} />
<PageHeading
title={t("title", { ns: "login" })}
subtitle={t("subtitle", { ns: "login" })}
/>
<Card className="max-w-md w-full p-5 sm:p-10 text-left">
<form
data-testid="sign-in-form"
@ -51,12 +53,16 @@ function Main() {
/>
<div className="flex flex-col items-center justify-center mt-2 gap-2">
<Button type="submit" isLoading={isPending}>
<Button
data-testid="submit-login"
type="submit"
isLoading={isPending}
>
{t("loginButton")}
</Button>
<PasswordForgotten setEmail={setEmail} email={email} />
<Link href="/signup">{t("signup",{ ns: 'login' })}</Link>
<Link href="/signup">{t("signup", { ns: "login" })}</Link>
</div>
<Divider text={t("or")} />
@ -69,7 +75,7 @@ function Main() {
</Card>
</section>
</main>
)
);
}
export default function Login() {
@ -77,6 +83,5 @@ export default function Login() {
<Suspense fallback="Loading...">
<Main />
</Suspense>
);
}

11
frontend/e2e/index.ts Normal file
View File

@ -0,0 +1,11 @@
import { test } from "@playwright/test";
import { chatTests } from "./tests/chat";
import { createBrainTests } from "./tests/createBrain";
import { uploadTests } from "./tests/upload";
test.describe(createBrainTests);
test.describe.skip(uploadTests);
test.describe.skip(chatTests);

View File

@ -0,0 +1,13 @@
import { test } from "@playwright/test";
import { login } from "../utils/login";
export const chatTests = (): void => {
test("chat", async ({ page }) => {
await login(page);
await page.goto("/chat");
await page.getByRole("combobox").locator("div").nth(2).click();
await page.getByRole("combobox").fill("Hello");
await page.getByTestId("submit-button").click();
});
};

View File

@ -0,0 +1,13 @@
import { test } from "@playwright/test";
import { login } from "../utils/login";
export const createBrainTests = (): void => {
test("create brain", async ({ page }) => {
await login(page);
await page.getByTestId("brain-management-button").first().click();
await page.getByTestId("add-brain-button").click();
await page.getByTestId("brain-name").fill("Test brain");
await page.getByTestId("create-brain-submit-button").click();
});
};

View File

@ -0,0 +1,19 @@
import { test } from "@playwright/test";
import { login } from "../utils/login";
export const uploadTests = (): void => {
test("upload", async ({ page }) => {
await login(page);
await page.goto("/chat");
await page.getByTestId("upload-button").click();
await page
.getByRole("button", {
name: "Drag and drop files here, or click to browse",
})
.click();
await page.getByPlaceholder("Insert website URL").click();
await page.getByPlaceholder("Insert website URL").fill("https://quivr.app");
await page.getByPlaceholder("Insert website URL").press("Enter");
});
};

View File

@ -0,0 +1,24 @@
import { Page } from "@playwright/test";
export const login = async (page: Page): Promise<void> => {
const frontendUrl = process.env.NEXT_PUBLIC_E2E_URL;
const email = process.env.NEXT_PUBLIC_E2E_EMAIL;
const password = process.env.NEXT_PUBLIC_E2E_PASSWORD;
if (frontendUrl === undefined) {
throw new Error("NEXT_PUBLIC_E2E_URL is not defined");
}
if (email === undefined) {
throw new Error("NEXT_PUBLIC_E2E_EMAIL is not defined");
}
if (password === undefined) {
throw new Error("NEXT_PUBLIC_E2E_PASSWORD is not defined");
}
await page.goto(frontendUrl);
await page.getByTestId("login-button").first().click();
await page.getByPlaceholder("Email").fill(email);
await page.getByPlaceholder("Password").fill(password);
await page.getByTestId("submit-login").click();
await page.waitForURL(/chat/);
};

View File

@ -36,6 +36,7 @@ export const AddBrainModal = (): JSX.Element => {
onClick={() => void 0}
variant={"tertiary"}
className="border-0"
data-testid="add-brain-button"
>
{t("newBrain", { ns: "brain" })}
<MdAdd className="text-xl" />
@ -61,6 +62,7 @@ export const AddBrainModal = (): JSX.Element => {
autoComplete="off"
className="flex-1"
required
data-testid="brain-name"
{...register("name")}
/>
@ -152,7 +154,12 @@ export const AddBrainModal = (): JSX.Element => {
</label>
</div>
<Button isLoading={isPending} className="mt-12 self-end" type="submit">
<Button
isLoading={isPending}
className="mt-12 self-end"
type="submit"
data-testid="create-brain-submit-button"
>
{t("createButton")}
<MdAdd className="text-xl" />
</Button>

View File

@ -7,7 +7,7 @@ import Button from "@/lib/components/ui/Button";
export const AuthButtons = (): JSX.Element => {
const pathname = usePathname();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {t, i18n} = useTranslation();
const { t } = useTranslation();
if (pathname === "/signup") {
return (
@ -16,19 +16,19 @@ export const AuthButtons = (): JSX.Element => {
</Link>
);
}
else if (pathname === "/login") {
if (pathname === "/login") {
return (
<Link href={"/signup"}>
<Button variant={"secondary"}>{t("signUpButton")}</Button>
</Link>
)
} else {
return (
<Link href={"/login"}>
<Button variant={"secondary"}>{t("loginButton")}</Button>
</Link>
);
}
return (
<Link href={"/login"}>
<Button data-testid="login-button" variant={"secondary"}>
{t("loginButton")}
</Button>
</Link>
);
};

View File

@ -13,6 +13,7 @@ export const BrainManagementButton = (): JSX.Element => {
variant={"tertiary"}
className="focus:outline-none text-2xl"
aria-label="Settings"
data-testid="brain-management-button"
>
<FaBrain className="w-6 h-6" />
</Button>

View File

@ -9,6 +9,7 @@
"lint": "next lint",
"lint-fix": "eslint --fix .",
"test-unit": "vitest run",
"test-e2e": "npx playwright test",
"test-type": "tsc --noEmit --emitDeclarationOnly false",
"test": "yarn test-type",
"precommit": "yarn lint && yarn test",
@ -78,6 +79,7 @@
"victory": "^36.6.10"
},
"devDependencies": {
"@playwright/test": "^1.38.0",
"@tailwindcss/typography": "^0.5.9",
"@testing-library/react": "^14.0.0",
"@vitejs/plugin-react": "^4.0.1",
@ -87,4 +89,4 @@
"react-icons": "^4.8.0",
"vitest": "^0.32.2"
}
}
}

View File

@ -0,0 +1,27 @@
import { defineConfig, devices } from "@playwright/test";
import dotenv from "dotenv";
dotenv.config();
export default defineConfig({
testDir: "./e2e",
fullyParallel: true,
forbidOnly: !(process.env.CI == null),
retries: process.env.CI != null ? 2 : 0,
workers: 1,
reporter: "html",
testMatch: "e2e/index.ts",
use: {
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
webServer: {
command:
process.env.NODE_ENV === "production"
? "yarn run build && yarn run start -p 3003"
: "yarn run dev -p 3003",
},
});

File diff suppressed because it is too large Load Diff