diff --git a/components/team/MemberInvitationModal.tsx b/components/team/MemberInvitationModal.tsx
index 2ba1d2dba2..30f43f2214 100644
--- a/components/team/MemberInvitationModal.tsx
+++ b/components/team/MemberInvitationModal.tsx
@@ -125,7 +125,7 @@ export default function MemberInvitationModal(props: { team: TeamWithMembers | n
)}
-
diff --git a/package.json b/package.json
index ea3ca758c9..7027c96977 100644
--- a/package.json
+++ b/package.json
@@ -116,6 +116,7 @@
"@types/jest": "^27.0.3",
"@types/lodash": "^4.14.177",
"@types/micro": "^7.3.6",
+ "@types/module-alias": "^2.0.1",
"@types/node": "^16.11.10",
"@types/nodemailer": "^6.4.4",
"@types/qrcode": "^1.4.1",
@@ -140,6 +141,7 @@
"jest": "^26.0.0",
"lint-staged": "^11.1.2",
"mockdate": "^3.0.5",
+ "module-alias": "^2.2.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.4",
"prettier": "^2.3.2",
diff --git a/pages/api/auth/signup.ts b/pages/api/auth/signup.ts
index 45d351a487..5c92ddf1d1 100644
--- a/pages/api/auth/signup.ts
+++ b/pages/api/auth/signup.ts
@@ -31,21 +31,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
+ // There is actually an existingUser if username matches
+ // OR if email matches and both username and password are set
const existingUser = await prisma.user.findFirst({
where: {
OR: [
+ { username },
{
- username: username,
- },
- {
- email: userEmail,
- },
- ],
- AND: [
- {
- emailVerified: {
- not: null,
- },
+ AND: [{ email: userEmail }, { password: { not: null } }, { username: { not: null } }],
},
],
},
diff --git a/pages/settings/teams/[id].tsx b/pages/settings/teams/[id].tsx
index 6064fdc974..beb44ed2bf 100644
--- a/pages/settings/teams/[id].tsx
+++ b/pages/settings/teams/[id].tsx
@@ -72,7 +72,8 @@ export function TeamSettingsPage() {
type="button"
color="secondary"
StartIcon={PlusIcon}
- onClick={() => setShowMemberInvitationModal(true)}>
+ onClick={() => setShowMemberInvitationModal(true)}
+ data-testid="new-member-button">
{t("new_member")}
diff --git a/playwright.config.ts b/playwright.config.ts
index 82c824f154..1ee266ae47 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -1,4 +1,17 @@
import { PlaywrightTestConfig, devices } from "@playwright/test";
+import { addAliases } from "module-alias";
+
+// Add aliases for the paths specified in the tsconfig.json file.
+// This is needed because playwright does not consider tsconfig.json
+// For more info, see:
+// https://stackoverflow.com/questions/69023682/typescript-playwright-error-cannot-find-module
+// https://github.com/microsoft/playwright/issues/7066#issuecomment-983984496
+addAliases({
+ "@components": __dirname + "/components",
+ "@lib": __dirname + "/lib",
+ "@server": __dirname + "/server",
+ "@ee": __dirname + "/ee",
+});
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
diff --git a/playwright/auth/auth-index.test.ts b/playwright/auth/auth-index.test.ts
index 6b46733d6d..674a1d2970 100644
--- a/playwright/auth/auth-index.test.ts
+++ b/playwright/auth/auth-index.test.ts
@@ -1,5 +1,105 @@
+import { expect, test } from "@playwright/test";
+
+import { BASE_URL } from "@lib/config/constants";
+import prisma from "@lib/prisma";
+
import { todo } from "../lib/testUtils";
-todo("Can signup from a team invite");
+test.describe("Can signup from a team invite", async () => {
+ let page;
+ let token: string | undefined;
+ let signupFromInviteURL = "";
+ const team = { name: "Seeded Team", slug: "seeded-team" };
+ const testUser = {
+ email: "test@test.com",
+ password: "secretpassword123",
+ validUsername: "test-user",
+ };
+ const usernameAlreadyTaken = "teampro";
+ const emailAlreadyTaken = "teampro@example.com";
+
+ test.use({ storageState: "playwright/artifacts/teamproStorageState.json" });
+ test.beforeAll(async ({ browser }) => {
+ page = await browser.newPage();
+
+ await page.goto("/settings/teams");
+
+ await page.waitForSelector(`a[title="${team.name}"]`);
+ await page.click(`a[title="${team.name}"]`);
+
+ // Send invite to team
+ await page.click('[data-testid="new-member-button"]');
+ await page.fill('input[id="inviteUser"]', testUser.email);
+ await page.click('[data-testid="invite-new-member-button"]');
+
+ // Wait for the invite to be sent
+ await page.waitForSelector(`[data-testid="member-email"][data-email="${testUser.email}"]`);
+
+ const tokenObj = await prisma.verificationRequest.findFirst({
+ where: { identifier: testUser.email },
+ select: { token: true },
+ });
+ token = tokenObj?.token;
+ signupFromInviteURL = `/auth/signup?token=${token}&callbackUrl=${BASE_URL}/settings/teams`;
+ });
+
+ test.afterAll(async () => {
+ // Delete test user
+ await prisma.user.delete({
+ where: { email: testUser.email },
+ });
+ // Delete verification request
+ await prisma.verificationRequest.delete({
+ where: { token },
+ });
+ });
+
+ test("Username already taken", async ({ page }) => {
+ expect(token).toBeDefined();
+ await page.goto(signupFromInviteURL);
+ // Fill in form
+ await page.fill('input[name="username"]', usernameAlreadyTaken);
+ await page.fill('input[name="email"]', testUser.email);
+ await page.fill('input[name="password"]', testUser.password);
+ await page.fill('input[name="passwordcheck"]', testUser.password);
+ await page.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit
+
+ await expect(page.locator('text="Username already taken"')).toBeVisible();
+ });
+
+ test("Email address is already registered", async ({ page }) => {
+ expect(token).toBeDefined();
+ await page.goto(signupFromInviteURL);
+ // Fill in form
+ await page.fill('input[name="username"]', testUser.validUsername);
+ await page.fill('input[name="email"]', emailAlreadyTaken);
+ await page.fill('input[name="password"]', testUser.password);
+ await page.fill('input[name="passwordcheck"]', testUser.password);
+ await page.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit
+
+ await expect(page.locator('text="Email address is already registered"')).toBeVisible();
+ });
+
+ test("Successful signup", async ({ page }) => {
+ expect(token).toBeDefined();
+ await page.goto(signupFromInviteURL);
+ // Fill in form
+ await page.fill('input[name="username"]', testUser.validUsername);
+ await page.fill('input[name="email"]', testUser.email);
+ await page.fill('input[name="password"]', testUser.password);
+ await page.fill('input[name="passwordcheck"]', testUser.password);
+ await page.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit
+
+ await page.waitForNavigation();
+
+ const createdUser = await prisma.user.findUnique({
+ where: { email: testUser.email },
+ });
+ expect(createdUser).not.toBeNull();
+ expect(createdUser?.username).toBe(testUser.validUsername);
+ expect(createdUser?.password).not.toBeNull();
+ expect(createdUser?.emailVerified).not.toBeNull();
+ });
+});
todo("Can login using 2FA");
diff --git a/playwright/lib/globalSetup.ts b/playwright/lib/globalSetup.ts
index 368ec7c6be..91e86ebe0c 100644
--- a/playwright/lib/globalSetup.ts
+++ b/playwright/lib/globalSetup.ts
@@ -33,7 +33,7 @@ async function globalSetup(/* config: FullConfig */) {
await loginAsUser("free", browser);
// await loginAsUser("usa", browser);
// await loginAsUser("teamfree", browser);
- // await loginAsUser("teampro", browser);
+ await loginAsUser("teampro", browser);
await browser.close();
}
diff --git a/yarn.lock b/yarn.lock
index a73431481c..f185c3ee6e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2589,6 +2589,11 @@
"@types/node" "*"
"@types/socket.io" "2.1.13"
+"@types/module-alias@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.1.tgz#e5893236ce922152d57c5f3f978f764f4deeb45f"
+ integrity sha512-DN/CCT1HQG6HquBNJdLkvV+4v5l/oEuwOHUPLxI+Eub0NED+lk0YUfba04WGH90EINiUrNgClkNnwGmbICeWMQ==
+
"@types/ms@*":
version "0.7.31"
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
@@ -7548,6 +7553,11 @@ mockdate@^3.0.5:
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==
+module-alias@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
+ integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
+
mongodb-connection-string-url@^2.3.2:
version "2.4.1"
resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz#6b3c6c40133a0ad059fe9a0abda64b2a1cb4e8b4"