158 lines
7.4 KiB
TypeScript
158 lines
7.4 KiB
TypeScript
|
|
import { expect, test } from "@playwright/test";
|
||
|
|
import { captureScreenshot, completeOnboarding, signup } from "./helpers";
|
||
|
|
|
||
|
|
test("logout protects bot deep links and sign-in restores the session", async ({
|
||
|
|
page,
|
||
|
|
}, testInfo) => {
|
||
|
|
const stamp = Date.now();
|
||
|
|
const email = `auth-lifecycle-${stamp}@rakazo.test`;
|
||
|
|
const password = "password12";
|
||
|
|
const userName = "Auth Lifecycle";
|
||
|
|
|
||
|
|
await page.goto("/sign-up");
|
||
|
|
await expect(page.getByLabel("Name")).toHaveAttribute("autocomplete", "name");
|
||
|
|
await expect(page.getByLabel("Email")).toHaveAttribute("autocomplete", "username");
|
||
|
|
await expect(page.getByLabel("Password", { exact: true })).toHaveAttribute(
|
||
|
|
"autocomplete",
|
||
|
|
"new-password",
|
||
|
|
);
|
||
|
|
|
||
|
|
await signup(page, email, password, userName);
|
||
|
|
await completeOnboarding(page);
|
||
|
|
|
||
|
|
await page.waitForURL(/\/app\/[^/]+$/);
|
||
|
|
const protectedBotPath = new URL(page.url()).pathname;
|
||
|
|
await expect(page.getByPlaceholder("Message Chief")).toBeVisible();
|
||
|
|
|
||
|
|
await page.getByRole("button", { name: new RegExp(userName, "i") }).click();
|
||
|
|
await expect(page.getByRole("button", { name: "Log out" })).toBeVisible();
|
||
|
|
await captureScreenshot(page, testInfo, "36-account-menu");
|
||
|
|
|
||
|
|
await page.getByRole("button", { name: "Log out" }).click();
|
||
|
|
await expect(page.getByRole("heading", { name: "Sign in to BangSo Bot" })).toBeVisible();
|
||
|
|
await page.goto("/");
|
||
|
|
await expect(page.getByText(/Your team of always-on agents/)).toBeVisible();
|
||
|
|
await expect(page.getByRole("button", { name: /Sign in/ })).toBeVisible();
|
||
|
|
await captureScreenshot(page, testInfo, "37-logged-out-welcome");
|
||
|
|
|
||
|
|
await page.goto(protectedBotPath);
|
||
|
|
await page.waitForURL((url) => url.pathname === "/sign-in");
|
||
|
|
await expect(page.getByRole("heading", { name: "Sign in to BangSo Bot" })).toBeVisible();
|
||
|
|
await expect(page.getByText("Chief", { exact: true })).toHaveCount(0);
|
||
|
|
await expect(page.getByText(userName, { exact: true })).toHaveCount(0);
|
||
|
|
await expect(page.getByLabel("Email")).toHaveAttribute("autocomplete", "username");
|
||
|
|
await expect(page.getByLabel("Password", { exact: true })).toHaveAttribute(
|
||
|
|
"autocomplete",
|
||
|
|
"current-password",
|
||
|
|
);
|
||
|
|
await captureScreenshot(page, testInfo, "38-protected-deep-link-sign-in");
|
||
|
|
|
||
|
|
await page.getByPlaceholder("Your email address").fill(email);
|
||
|
|
await page.getByPlaceholder("Password").fill("wrong-password12");
|
||
|
|
await page.getByRole("button", { name: "Continue with email" }).click();
|
||
|
|
await expect(
|
||
|
|
page
|
||
|
|
.locator("form")
|
||
|
|
.getByText(/invalid email or password|invalid credentials|incorrect password/i),
|
||
|
|
).toBeVisible();
|
||
|
|
await expect(page).toHaveURL(/\/sign-in$/);
|
||
|
|
await captureScreenshot(page, testInfo, "39-invalid-credentials");
|
||
|
|
|
||
|
|
await page.getByPlaceholder("Password").fill(password);
|
||
|
|
await page.getByRole("button", { name: "Continue with email" }).click();
|
||
|
|
await page.waitForURL((url) => url.pathname === protectedBotPath, {
|
||
|
|
timeout: 20_000,
|
||
|
|
});
|
||
|
|
const composer = page.getByRole("combobox", { name: "Message Chief" });
|
||
|
|
await expect(composer).toHaveAttribute("name", "chat-message");
|
||
|
|
await expect(composer).toHaveAttribute("autocomplete", "off");
|
||
|
|
await expect(composer).toHaveAttribute("aria-label", "Message Chief");
|
||
|
|
await expect(page.getByRole("button", { name: new RegExp(userName, "i") })).toBeVisible();
|
||
|
|
|
||
|
|
await composer.fill("line one");
|
||
|
|
const heightBeforeNewline = await composer.evaluate((el) => el.getBoundingClientRect().height);
|
||
|
|
await composer.press("Shift+Enter");
|
||
|
|
await composer.type("line two");
|
||
|
|
await expect(composer).toHaveValue("line one\nline two");
|
||
|
|
const heightWithNewline = await composer.evaluate((el) => el.getBoundingClientRect().height);
|
||
|
|
expect(heightWithNewline).toBeGreaterThan(heightBeforeNewline);
|
||
|
|
|
||
|
|
await composer.press("Enter");
|
||
|
|
const multilineMessage = page
|
||
|
|
.getByTestId("transcript")
|
||
|
|
.getByText("line one\nline two", { exact: true });
|
||
|
|
await expect(multilineMessage).toBeVisible();
|
||
|
|
await expect(multilineMessage).toHaveCSS("white-space", "pre-wrap");
|
||
|
|
|
||
|
|
const message = "Fake composer regression check.";
|
||
|
|
await composer.fill(message);
|
||
|
|
await captureScreenshot(page, testInfo, "40-restored-auth-session");
|
||
|
|
await composer.press("Enter");
|
||
|
|
await expect(composer).toHaveValue("");
|
||
|
|
// Scope to the transcript: the sidebar activity row can echo the same text.
|
||
|
|
await expect(page.getByTestId("transcript").getByText(message, { exact: true })).toBeVisible();
|
||
|
|
});
|
||
|
|
|
||
|
|
test("changes and recovers an email password", async ({ page }, testInfo) => {
|
||
|
|
const stamp = Date.now();
|
||
|
|
const email = `password-recovery-${stamp}@rakazo.test`;
|
||
|
|
const originalPassword = "password12";
|
||
|
|
const changedPassword = "changed-password12";
|
||
|
|
const resetPassword = "reset-password12";
|
||
|
|
const userName = "Password Recovery";
|
||
|
|
|
||
|
|
await signup(page, email, originalPassword, userName);
|
||
|
|
await completeOnboarding(page);
|
||
|
|
await page.waitForURL(/\/app\/[^/]+$/);
|
||
|
|
|
||
|
|
await page.getByTestId("user-menu-trigger").click();
|
||
|
|
await page.getByRole("button", { name: "Settings", exact: true }).click();
|
||
|
|
const settings = page.getByTestId("user-settings");
|
||
|
|
await expect(settings).toBeVisible();
|
||
|
|
await settings.getByLabel("Current password").fill(originalPassword);
|
||
|
|
await settings.getByLabel("New password").fill(changedPassword);
|
||
|
|
await settings.getByLabel("Confirm password").fill(changedPassword);
|
||
|
|
await settings.getByRole("button", { name: "Change password" }).click();
|
||
|
|
await expect(settings.getByText("Password updated")).toBeVisible();
|
||
|
|
await captureScreenshot(page, testInfo, "41-password-changed");
|
||
|
|
await settings.getByRole("button", { name: "Close user settings" }).click();
|
||
|
|
|
||
|
|
await page.getByRole("button", { name: new RegExp(userName, "i") }).click();
|
||
|
|
await page.getByRole("button", { name: "Log out" }).click();
|
||
|
|
await expect(page.getByRole("link", { name: "Forgot password?" })).toBeVisible();
|
||
|
|
await page.getByRole("link", { name: "Forgot password?" }).click();
|
||
|
|
await expect(page.getByRole("heading", { name: "Reset your password" })).toBeVisible();
|
||
|
|
await page.getByLabel("Email").fill(email);
|
||
|
|
await expect(page.getByLabel("Email")).toHaveValue(email);
|
||
|
|
await page.getByRole("button", { name: "Send reset link" }).click();
|
||
|
|
await expect(page.getByText("Check your email")).toBeVisible();
|
||
|
|
await captureScreenshot(page, testInfo, "42-password-reset-requested");
|
||
|
|
|
||
|
|
const emailApi = process.env.API_URL ?? "http://127.0.0.1:3110";
|
||
|
|
await expect
|
||
|
|
.poll(async () => {
|
||
|
|
const response = await page.request.get(`${emailApi}/__e2e/emails`);
|
||
|
|
return ((await response.json()) as unknown[]).length;
|
||
|
|
})
|
||
|
|
.toBeGreaterThan(0);
|
||
|
|
const messagesResponse = await page.request.get(`${emailApi}/__e2e/emails`);
|
||
|
|
expect(messagesResponse.headers()["cache-control"]).toBe("no-store");
|
||
|
|
const messages = (await messagesResponse.json()) as Array<{
|
||
|
|
text: string;
|
||
|
|
}>;
|
||
|
|
const resetUrl = messages.at(-1)?.text.match(/https?:\/\/\S+/)?.[0];
|
||
|
|
expect(resetUrl).toBeTruthy();
|
||
|
|
|
||
|
|
await page.goto(resetUrl!);
|
||
|
|
await expect(page.getByRole("heading", { name: "Choose a new password" })).toBeVisible();
|
||
|
|
await page.getByLabel("New password").fill(resetPassword);
|
||
|
|
await page.getByLabel("Confirm password").fill(resetPassword);
|
||
|
|
await page.getByRole("button", { name: "Reset password" }).click();
|
||
|
|
await expect(page.getByText("Password updated")).toBeVisible();
|
||
|
|
await page.getByRole("link", { name: "Sign in" }).click();
|
||
|
|
await page.getByLabel("Email").fill(email);
|
||
|
|
await page.getByLabel("Password", { exact: true }).fill(resetPassword);
|
||
|
|
await page.getByRole("button", { name: "Continue with email" }).click();
|
||
|
|
await page.waitForURL(/\/app(?:\/|$)/);
|
||
|
|
});
|