chore: Add team invite tests (#12425)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
parent
3be6ed49e9
commit
4a9cd4007a
|
@ -0,0 +1,29 @@
|
|||
import type { Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
import { JSDOM } from "jsdom";
|
||||
import type { API, Messages } from "mailhog";
|
||||
|
||||
import { getEmailsReceivedByUser } from "../lib/testUtils";
|
||||
|
||||
export async function expectInvitationEmailToBeReceived(
|
||||
page: Page,
|
||||
emails: API | undefined,
|
||||
userEmail: string,
|
||||
subject: string,
|
||||
returnLink?: string
|
||||
) {
|
||||
if (!emails) return null;
|
||||
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||
await page.waitForTimeout(10000);
|
||||
const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail });
|
||||
expect(receivedEmails?.total).toBe(1);
|
||||
|
||||
const [firstReceivedEmail] = (receivedEmails as Messages).items;
|
||||
|
||||
expect(firstReceivedEmail.subject).toBe(subject);
|
||||
if (!returnLink) return;
|
||||
const dom = new JSDOM(firstReceivedEmail.html);
|
||||
const anchor = dom.window.document.querySelector(`a[href*="${returnLink}"]`);
|
||||
return anchor?.getAttribute("href");
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
import { expect } from "@playwright/test";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
import { test } from "../lib/fixtures";
|
||||
import { localize } from "../lib/testUtils";
|
||||
import { expectInvitationEmailToBeReceived } from "./expects";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
test.afterEach(async ({ users, emails, clipboard }) => {
|
||||
clipboard.reset();
|
||||
await users.deleteAll();
|
||||
emails?.deleteAll();
|
||||
});
|
||||
|
||||
test.describe("Team", () => {
|
||||
test("Invitation (non verified)", async ({ browser, page, users, emails, clipboard }) => {
|
||||
const t = await localize("en");
|
||||
const teamOwner = await users.create(undefined, { hasTeam: true });
|
||||
const { team } = await teamOwner.getFirstTeam();
|
||||
await teamOwner.apiLogin();
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await test.step("To the team by email (external user)", async () => {
|
||||
const invitedUserEmail = `rick_${Date.now()}@domain-${Date.now()}.com`;
|
||||
await page.locator(`button:text("${t("add")}")`).click();
|
||||
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
|
||||
await page.locator(`button:text("${t("send_invite")}")`).click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${team.name}'s admin invited you to join the team ${team.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
//Check newly invited member exists and is pending
|
||||
await expect(
|
||||
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
|
||||
).toHaveCount(1);
|
||||
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (!inviteLink) return null;
|
||||
|
||||
// Follow invite link to new window
|
||||
const context = await browser.newContext();
|
||||
const newPage = await context.newPage();
|
||||
await newPage.goto(inviteLink);
|
||||
await newPage.waitForLoadState("networkidle");
|
||||
|
||||
// Check required fields
|
||||
await newPage.locator("button[type=submit]").click();
|
||||
await expect(newPage.locator('[data-testid="hint-error"]')).toHaveCount(3);
|
||||
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await newPage.locator("button[type=submit]").click();
|
||||
await newPage.waitForURL("/getting-started?from=signup");
|
||||
await newPage.close();
|
||||
await context.close();
|
||||
|
||||
// Check newly invited member is not pending anymore
|
||||
await page.bringToFront();
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(
|
||||
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
await test.step("To the team by invite link", async () => {
|
||||
const user = await users.create({
|
||||
email: `user-invite-${Date.now()}@domain.com`,
|
||||
password: "P4ssw0rd!",
|
||||
});
|
||||
await page.locator(`button:text("${t("add")}")`).click();
|
||||
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
|
||||
const inviteLink = await clipboard.get();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
const context = await browser.newContext();
|
||||
const inviteLinkPage = await context.newPage();
|
||||
await inviteLinkPage.goto(inviteLink);
|
||||
await inviteLinkPage.waitForLoadState("domcontentloaded");
|
||||
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
await expect(inviteLinkPage.locator('[data-testid="field-error"]')).toHaveCount(2);
|
||||
|
||||
await inviteLinkPage.locator("input[name=email]").fill(user.email);
|
||||
await inviteLinkPage.locator("input[name=password]").fill(user.username || "P4ssw0rd!");
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
|
||||
await inviteLinkPage.waitForURL(`${WEBAPP_URL}/teams**`);
|
||||
});
|
||||
});
|
||||
|
||||
test("Invitation (verified)", async ({ browser, page, users, emails }) => {
|
||||
const t = await localize("en");
|
||||
const teamOwner = await users.create({ name: `team-owner-${Date.now()}` }, { hasTeam: true });
|
||||
const { team } = await teamOwner.getFirstTeam();
|
||||
await teamOwner.apiLogin();
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await test.step("To the organization by email (internal user)", async () => {
|
||||
const invitedUserEmail = `rick@example.com`;
|
||||
await page.locator(`button:text("${t("add")}")`).click();
|
||||
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
|
||||
await page.locator(`button:text("${t("send_invite")}")`).click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${teamOwner.name} invited you to join the team ${team.name} on Cal.com`
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
|
||||
).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -152,7 +152,14 @@ export default function MemberListItem(props: Props) {
|
|||
{props.member.role && <TeamRole role={props.member.role} />}
|
||||
</div>
|
||||
<div className="text-default flex items-center">
|
||||
<span className=" block text-sm" data-testid="member-email" data-email={props.member.email}>
|
||||
<span
|
||||
className=" block text-sm"
|
||||
data-testid={
|
||||
props.member.accepted
|
||||
? "member-email"
|
||||
: `email-${props.member.email.replace("@", "")}-pending`
|
||||
}
|
||||
data-email={props.member.email}>
|
||||
{props.member.email}
|
||||
</span>
|
||||
{bookingLink && (
|
||||
|
|
|
@ -50,7 +50,10 @@ export function HintsOrErrors<T extends FieldValues = FieldValues>({
|
|||
return (
|
||||
<li
|
||||
key={key}
|
||||
className={error !== undefined ? (submitted ? "text-red-700" : "") : "text-green-600"}>
|
||||
data-testid="hint-error"
|
||||
className={
|
||||
error !== undefined ? (submitted ? "bg-yellow-200 text-red-700" : "") : "text-green-600"
|
||||
}>
|
||||
{error !== undefined ? (
|
||||
submitted ? (
|
||||
<X size="12" strokeWidth="3" className="-ml-1 inline-block ltr:mr-2 rtl:ml-2" />
|
||||
|
@ -72,7 +75,9 @@ export function HintsOrErrors<T extends FieldValues = FieldValues>({
|
|||
// errors exist, not custom ones, just show them as is
|
||||
if (fieldErrors) {
|
||||
return (
|
||||
<div className="text-gray mt-2 flex items-center gap-x-2 text-sm text-red-700">
|
||||
<div
|
||||
data-testid="field-error"
|
||||
className="text-gray mt-2 flex items-center gap-x-2 text-sm text-red-700">
|
||||
<div>
|
||||
<Info className="h-3 w-3" />
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user