feat: adds e2e test for invite functionality (#1846)

This commit is contained in:
Anshuman Pandey
2024-01-08 17:34:25 +05:30
committed by GitHub
parent f23b4f63fa
commit abe98be561
8 changed files with 171 additions and 11 deletions

View File

@@ -312,7 +312,7 @@ export default function Navigation({
{/* User Dropdown */}
<div className="hidden lg:ml-6 lg:flex lg:items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger asChild id="userDropdownTrigger">
<div tabIndex={0} className="flex cursor-pointer flex-row items-center space-x-5">
{session.user.imageUrl ? (
<Image
@@ -335,7 +335,7 @@ export default function Navigation({
<ChevronDownIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuContent className="w-56" id="userDropdownContentWrapper">
<DropdownMenuLabel className="cursor-default break-all">
<span className="ph-no-capture font-normal">Signed in as </span>
{session?.user?.name && session?.user?.name.length > 30 ? (

View File

@@ -79,15 +79,21 @@ export default function AddMemberModal({
<div className="flex justify-between rounded-lg p-6">
<div className="w-full space-y-4">
<div>
<Label>Full Name</Label>
<Label htmlFor="memberNameInput">Full Name</Label>
<Input
id="memberNameInput"
placeholder="e.g. Hans Wurst"
{...register("name", { required: true, validate: (value) => value.trim() !== "" })}
/>
</div>
<div>
<Label>Email Adress</Label>
<Input type="email" placeholder="hans@wurst.com" {...register("email", { required: true })} />
<Label htmlFor="memberEmailInput">Email Address</Label>
<Input
id="memberEmailInput"
type="email"
placeholder="hans@wurst.com"
{...register("email", { required: true })}
/>
</div>
{canDoRoleManagement && <AddMemberRole control={control} />}
</div>

View File

@@ -97,7 +97,7 @@ export default function MemberActions({ team, member, invite, showDeleteButton }
return (
<>
{showDeleteButton && (
<button onClick={() => setDeleteMemberModalOpen(true)}>
<button id="deleteMemberButton" onClick={() => setDeleteMemberModalOpen(true)}>
<TrashIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
</button>
)}
@@ -109,7 +109,8 @@ export default function MemberActions({ team, member, invite, showDeleteButton }
<button
onClick={() => {
handleShareInvite();
}}>
}}
id="shareInviteButton">
<ShareIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
</button>
</TooltipTrigger>
@@ -122,7 +123,8 @@ export default function MemberActions({ team, member, invite, showDeleteButton }
<button
onClick={() => {
handleResendInvite();
}}>
}}
id="resendInviteButton">
<PaperAirplaneIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
</button>
</TooltipTrigger>

View File

@@ -36,10 +36,10 @@ const MembersInfo = async ({
const allMembers = [...members, ...invites];
return (
<div className="grid-cols-20">
<div className="grid-cols-20" id="membersInfoWrapper">
{allMembers.map((member) => (
<div
className="grid-cols-20 grid h-auto w-full content-center rounded-lg p-0.5 py-2 text-left text-sm text-slate-900"
className="singleMemberInfo grid-cols-20 grid h-auto w-full content-center rounded-lg p-0.5 py-2 text-left text-sm text-slate-900"
key={member.email}>
<div className="h-58 col-span-2 pl-4">
{isInvitee(member) ? (

View File

@@ -43,7 +43,8 @@ export default function ShareInviteModal({ inviteToken, open, setOpen }: ShareIn
<p
ref={linkTextRef}
className="relative mt-3 w-full truncate rounded-lg border border-slate-300 bg-slate-50 p-3 text-center text-slate-800"
onClick={() => handleTextSelection()}>
onClick={() => handleTextSelection()}
id="inviteLinkText">
{`${window.location.protocol}//${window.location.host}/invite?token=${inviteToken}`}
</p>
</div>

View File

@@ -0,0 +1,112 @@
import { expect, test } from "playwright/test";
import { login, signUpAndLogin, signupUsingInviteToken, skipOnboarding } from "./utils/helper";
import { invites, users } from "./utils/mock";
test.describe("Invite, accept and remove team member", async () => {
test.describe.configure({ mode: "serial" });
const { email, password, name } = users.team[0];
let inviteLink: string;
test("Invite team member", async ({ page }) => {
await signUpAndLogin(page, name, email, password);
await skipOnboarding(page);
const dropdownTrigger = page.locator("#userDropdownTrigger");
await expect(dropdownTrigger).toBeVisible();
await dropdownTrigger.click();
const dropdownContentWrapper = page.locator("#userDropdownContentWrapper");
await expect(dropdownContentWrapper).toBeVisible();
await page.getByRole("link", { name: "Team" }).click();
// Add member button
await expect(page.getByRole("button", { name: "Add Member" })).toBeVisible();
await page.getByRole("button", { name: "Add Member" }).click();
// Fill the member name and email form
await expect(page.getByLabel("Email")).toBeVisible();
await page.getByLabel("Full Name").fill(invites.addMember.name);
await expect(page.getByLabel("Email Address")).toBeVisible();
await page.getByLabel("Email Address").fill(invites.addMember.email);
await page.getByRole("button", { name: "Send Invitation", exact: true }).click();
});
test("Copy Invite Link", async ({ page }) => {
await login(page, email, password);
const dropdownTrigger = page.locator("#userDropdownTrigger");
await expect(dropdownTrigger).toBeVisible();
await dropdownTrigger.click();
const dropdownContentWrapper = page.locator("#userDropdownContentWrapper");
await expect(dropdownContentWrapper).toBeVisible();
await page.getByRole("link", { name: "Team" }).click();
await expect(page.locator("#membersInfoWrapper")).toBeVisible();
const lastMemberInfo = page.locator("#membersInfoWrapper > .singleMemberInfo:last-child");
await expect(lastMemberInfo).toBeVisible();
const pendingSpan = lastMemberInfo.locator("span").filter({ hasText: "Pending" });
await expect(pendingSpan).toBeVisible();
const shareInviteButton = page.locator("#shareInviteButton");
await expect(shareInviteButton).toBeVisible();
await shareInviteButton.click();
const inviteLinkText = page.locator("#inviteLinkText");
await expect(inviteLinkText).toBeVisible();
// invite link text is a paragraph, and we need the text inside it
const inviteLinkTextContent = await inviteLinkText.textContent();
if (inviteLinkTextContent) {
inviteLink = inviteLinkTextContent;
}
});
test("Accept Invite", async ({ page }) => {
const { email, name, password } = users.team[1];
page.goto(inviteLink);
await page.waitForURL(/\/invite\?token=[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/);
// Create account button
await expect(page.getByRole("link", { name: "Create account" })).toBeVisible();
await page.getByRole("link", { name: "Create account" }).click();
await signupUsingInviteToken(page, name, email, password);
await skipOnboarding(page);
});
test("Remove Member", async ({ page }) => {
await login(page, email, password);
const dropdownTrigger = page.locator("#userDropdownTrigger");
await expect(dropdownTrigger).toBeVisible();
await dropdownTrigger.click();
const dropdownContentWrapper = page.locator("#userDropdownContentWrapper");
await expect(dropdownContentWrapper).toBeVisible();
await page.getByRole("link", { name: "Team" }).click();
await expect(page.locator("#membersInfoWrapper")).toBeVisible();
const lastMemberInfo = page.locator("#membersInfoWrapper > .singleMemberInfo:last-child");
await expect(lastMemberInfo).toBeVisible();
const deleteMemberButton = lastMemberInfo.locator("#deleteMemberButton");
await expect(deleteMemberButton).toBeVisible();
await deleteMemberButton.click();
await expect(page.getByRole("button", { name: "Delete", exact: true })).toBeVisible();
await page.getByRole("button", { name: "Delete", exact: true }).click();
});
});

View File

@@ -51,3 +51,23 @@ export const replaceEnvironmentIdInHtml = (filePath: string, environmentId: stri
writeFileSync(filePath, htmlContent);
return "file:///" + filePath;
};
export const signupUsingInviteToken = async (page: Page, name: string, email: string, password: string) => {
await page.getByRole("button", { name: "Continue with Email" }).click();
await page.getByPlaceholder("Full Name").fill(name);
await page.getByPlaceholder("Full Name").press("Tab");
// the email is already filled in the input field
const inputValue = await page.getByPlaceholder("work@email.com").inputValue();
expect(inputValue).toEqual(email);
await page.getByPlaceholder("work@email.com").press("Tab");
await page.getByPlaceholder("*******").fill(password);
await page.press('input[name="password"]', "Enter");
await page.getByRole("link", { name: "Login" }).click();
await page.getByRole("button", { name: "Login with Email" }).click();
await page.getByPlaceholder("work@email.com").fill(email);
await page.getByPlaceholder("*******").click();
await page.getByPlaceholder("*******").fill(password);
await page.getByRole("button", { name: "Login with Email" }).click();
};

View File

@@ -32,6 +32,18 @@ export const users = {
password: "XpP%X9UU3efj8vJa",
},
],
team: [
{
name: "Team User 1",
email: "team1@formbricks.com",
password: "Test#1234",
},
{
name: "Team User 2",
email: "team2@formbricks.com",
password: "Test#1234",
},
],
};
export const teams = {
@@ -97,3 +109,10 @@ export const surveys = {
},
},
};
export const invites = {
addMember: {
name: "Team User 2",
email: "team2@formbricks.com",
},
};