This repository has been archived by the owner on Jan 23, 2024. It is now read-only.
forked from calcom/cal.com
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: Add more orgs tests (calcom#12241)
- Loading branch information
1 parent
556b382
commit 48dde24
Showing
15 changed files
with
397 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type { Page } from "@playwright/test"; | ||
|
||
declare global { | ||
interface Window { | ||
E2E_CLIPBOARD_VALUE?: string; | ||
} | ||
} | ||
|
||
export type Window = typeof window; | ||
// creates the single server fixture | ||
export const createClipboardFixture = (page: Page) => { | ||
return { | ||
reset: async () => { | ||
await page.evaluate(() => { | ||
delete window.E2E_CLIPBOARD_VALUE; | ||
}); | ||
}, | ||
get: async () => { | ||
return getClipboardValue({ page }); | ||
}, | ||
}; | ||
}; | ||
|
||
function getClipboardValue({ page }: { page: Page }) { | ||
return page.evaluate(() => { | ||
return new Promise<string>((resolve, reject) => { | ||
setInterval(() => { | ||
if (!window.E2E_CLIPBOARD_VALUE) return; | ||
resolve(window.E2E_CLIPBOARD_VALUE); | ||
}, 500); | ||
setTimeout(() => reject(new Error("Timeout")), 1000); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { Page } from "@playwright/test"; | ||
import { expect } from "@playwright/test"; | ||
import { JSDOM } from "jsdom"; | ||
// eslint-disable-next-line no-restricted-imports | ||
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; | ||
// We need to wait for the email to go through, otherwise it will fail | ||
// eslint-disable-next-line playwright/no-wait-for-timeout | ||
await page.waitForTimeout(5000); | ||
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"); | ||
} |
143 changes: 143 additions & 0 deletions
143
apps/web/playwright/organization/organization-creation.e2e.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { expect } from "@playwright/test"; | ||
import path from "path"; | ||
|
||
import { test } from "../lib/fixtures"; | ||
import { generateTotpCode } from "../lib/testUtils"; | ||
import { expectInvitationEmailToBeReceived } from "./expects"; | ||
|
||
test.afterAll(({ users, emails }) => { | ||
users.deleteAll(); | ||
emails?.deleteAll(); | ||
}); | ||
|
||
function capitalize(text: string) { | ||
if (!text) { | ||
return text; | ||
} | ||
return text.charAt(0).toUpperCase() + text.slice(1); | ||
} | ||
|
||
test.describe("Organization", () => { | ||
test("should be able to create an organization and complete onboarding", async ({ | ||
page, | ||
users, | ||
emails, | ||
}) => { | ||
const orgOwner = await users.create(); | ||
const orgDomain = `${orgOwner.username}-org`; | ||
const orgName = capitalize(`${orgOwner.username}-org`); | ||
await orgOwner.apiLogin(); | ||
await page.goto("/settings/organizations/new"); | ||
await page.waitForLoadState("networkidle"); | ||
|
||
await test.step("Basic info", async () => { | ||
// Check required fields | ||
await page.locator("button[type=submit]").click(); | ||
await expect(page.locator(".text-red-700")).toHaveCount(3); | ||
|
||
// Happy path | ||
await page.locator("input[name=adminEmail]").fill(`john@${orgDomain}.com`); | ||
expect(await page.locator("input[name=name]").inputValue()).toEqual(orgName); | ||
expect(await page.locator("input[name=slug]").inputValue()).toEqual(orgDomain); | ||
await page.locator("button[type=submit]").click(); | ||
await page.waitForLoadState("networkidle"); | ||
|
||
// Check admin email about code verification | ||
await expectInvitationEmailToBeReceived( | ||
page, | ||
emails, | ||
`john@${orgOwner.username}-org.com`, | ||
"Verify your email to create an organization" | ||
); | ||
|
||
await test.step("Verification", async () => { | ||
// Code verification | ||
await expect(page.locator("#modal-title")).toBeVisible(); | ||
await page.locator("input[name='2fa1']").fill(generateTotpCode(`john@${orgDomain}.com`)); | ||
await page.locator("button:text('Verify')").click(); | ||
|
||
// Check admin email about DNS pending action | ||
await expectInvitationEmailToBeReceived( | ||
page, | ||
emails, | ||
"[email protected]", | ||
"New organization created: pending action" | ||
); | ||
|
||
// Waiting to be in next step URL | ||
await page.waitForURL("/settings/organizations/*/set-password"); | ||
}); | ||
}); | ||
|
||
await test.step("Admin password", async () => { | ||
// Check required fields | ||
await page.locator("button[type=submit]").click(); | ||
await expect(page.locator(".text-red-700")).toHaveCount(3); // 3 password hints | ||
|
||
// Happy path | ||
await page.locator("input[name='password']").fill("ADMIN_user2023$"); | ||
await page.locator("button[type=submit]").click(); | ||
|
||
// Waiting to be in next step URL | ||
await page.waitForURL("/settings/organizations/*/about"); | ||
}); | ||
|
||
await test.step("About the organization", async () => { | ||
// Choosing an avatar | ||
await page.locator('button:text("Upload")').click(); | ||
const fileChooserPromise = page.waitForEvent("filechooser"); | ||
await page.getByText("Choose a file...").click(); | ||
const fileChooser = await fileChooserPromise; | ||
await fileChooser.setFiles(path.join(__dirname, "../../public/apple-touch-icon.png")); | ||
await page.locator('button:text("Save")').click(); | ||
|
||
// About text | ||
await page.locator('textarea[name="about"]').fill("This is a testing org"); | ||
await page.locator("button[type=submit]").click(); | ||
|
||
// Waiting to be in next step URL | ||
await page.waitForURL("/settings/organizations/*/onboard-admins"); | ||
}); | ||
|
||
await test.step("On-board administrators", async () => { | ||
// Required field | ||
await page.locator("button[type=submit]").click(); | ||
|
||
// Happy path | ||
await page.locator('textarea[name="emails"]').fill(`rick@${orgDomain}.com`); | ||
await page.locator("button[type=submit]").click(); | ||
|
||
// Check if invited admin received the invitation email | ||
await expectInvitationEmailToBeReceived( | ||
page, | ||
emails, | ||
`rick@${orgDomain}.com`, | ||
`${orgName}'s admin invited you to join the organization ${orgName} on Cal.com` | ||
); | ||
|
||
// Waiting to be in next step URL | ||
await page.waitForURL("/settings/organizations/*/add-teams"); | ||
}); | ||
|
||
await test.step("Create teams", async () => { | ||
// Initial state | ||
await expect(page.locator('input[name="teams.0.name"]')).toHaveCount(1); | ||
await expect(page.locator('button:text("Continue")')).toBeDisabled(); | ||
|
||
// Filling one team | ||
await page.locator('input[name="teams.0.name"]').fill("Marketing"); | ||
await expect(page.locator('button:text("Continue")')).toBeEnabled(); | ||
|
||
// Adding another team | ||
await page.locator('button:text("Add a team")').click(); | ||
await expect(page.locator('button:text("Continue")')).toBeDisabled(); | ||
await expect(page.locator('input[name="teams.1.name"]')).toHaveCount(1); | ||
await page.locator('input[name="teams.1.name"]').fill("Sales"); | ||
await expect(page.locator('button:text("Continue")')).toBeEnabled(); | ||
|
||
// Finishing the creation wizard | ||
await page.locator('button:text("Continue")').click(); | ||
await page.waitForURL("/event-types"); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.