One playwright config to rule them all (#4072)

This commit is contained in:
Omar López 2022-09-01 20:00:48 -06:00 committed by GitHub
parent b09d727d3f
commit d27b7ab2c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 659 additions and 971 deletions

View File

@ -1,7 +1,7 @@
name: E2E App-Store Apps
on:
push:
branches: [ feature/event-routing ]
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request_target: # So we can test on forks
branches:
- main
@ -86,14 +86,14 @@ jobs:
restore-keys: cache-playwright-
- run: yarn --frozen-lockfile
- name: Install playwright deps
# if: steps.playwright-cache.outputs.cache-hit != 'true'
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- name: Run Tests
run: yarn app-e2e-quick
run: yarn test-e2e:app-store
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-core
path: packages/app-store/**/playwright/results
path: packages/app-store/**/playwright/results

View File

@ -79,7 +79,7 @@ jobs:
restore-keys: cache-playwright-
- run: yarn --frozen-lockfile
- name: Install playwright deps
# if: steps.playwright-cache.outputs.cache-hit != 'true'
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- name: Run Tests
run: yarn turbo run embed-tests-update-snapshots:ci --scope=@calcom/embed-react --concurrency=1

View File

@ -1,5 +1,7 @@
name: E2E test
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request_target: # So we can test on forks
branches:
- main

View File

@ -1,104 +0,0 @@
name: E2E Test - Integrations with Third Party
on:
push:
branches: [ tests/with-msw ]
pull_request_target: # So we can test on forks
branches:
- main
paths-ignore:
- apps/api/**
- apps/console/**
- apps/docs/**
- apps/swagger/**
- apps/website/**
- apps/web/public/**
jobs:
test:
timeout-minutes: 20
name: E2E Integration
strategy:
matrix:
node: ["16.x"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
env:
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
NEXT_PUBLIC_WEBAPP_URL: http://localhost:3000
NEXT_PUBLIC_WEBSITE_URL: http://localhost:3000
NEXTAUTH_SECRET: secret
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: true
# CRON_API_KEY: xxx
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
PAYMENT_FEE_PERCENTAGE: 0.005
PAYMENT_FEE_FIXED: 10
SAML_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
SAML_ADMINS: pro@example.com
NEXTAUTH_URL: http://localhost:3000/api/auth
ZOOM_CLIENT_ID: ZOOM_CLIENT_ID
ZOOM_CLIENT_SECRET: ZOOM_CLIENT_SECRET
HUBSPOT_CLIENT_ID: HUBSPOT_CLIENT_ID
HUBSPOT_CLIENT_SECRET: HUBSPOT_CLIENT_SECRET
NEXT_PUBLIC_IS_E2E: 1
# EMAIL_FROM: e2e@cal.com
# EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
# EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
# EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
# EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD }}
# MS_GRAPH_CLIENT_ID: xxx
# MS_GRAPH_CLIENT_SECRET: xxx
# ZOOM_CLIENT_ID: xxx
# ZOOM_CLIENT_SECRET: xxx
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
services:
postgres:
image: postgres:12.1
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
ports:
- 5432:5432
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 2
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: "yarn"
- name: Cache playwright binaries
uses: actions/cache@v2
id: playwright-cache
with:
path: |
~/Library/Caches/ms-playwright
~/.cache/ms-playwright
${{ github.workspace }}/node_modules/playwright
key: cache-playwright-${{ hashFiles('**/yarn.lock') }}
restore-keys: cache-playwright-
- run: yarn --frozen-lockfile
- name: Install playwright deps
# if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- name: Run Tests
# Force bypass cache because new environment variables were added that caused DB to change but build remains cached
run: yarn test-e2e-integrations --force
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-core
path: apps/web/playwright-integrations/test-results

View File

@ -1,5 +1,7 @@
name: "Meta Workflow: Require Conditional Status Checks"
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request:
branches:
- main
@ -34,3 +36,7 @@ jobs:
paths:
- /apps/web/**
- /packages/embeds/**
- job: test
paths:
- /apps/web/**
- /packages/**

28
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Unit tests
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request_target: # So we can test on forks
branches:
- main
paths:
- apps/web/**
- packages/**
jobs:
test:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 2
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
- name: Use Node 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "yarn"
- run: yarn --frozen-lockfile
- run: yarn test

View File

@ -207,7 +207,7 @@ Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If
yarn test-e2e
# To open last HTML report run:
yarn workspace @calcom/web playwright-report
yarn playwright show-report test-results/reports/playwright-html-report
```
### Upgrading from earlier versions

View File

@ -9,11 +9,6 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
"dev": "next dev",
"dx": "yarn dev",
"test-e2e": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --config=../../tests/config/playwright.config.ts --project=chromium",
"test-e2e-integrations": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --config=playwright-integrations/config/playwright.config.ts --project=chromium",
"test-e2e-integrations-quick": "QUICK=true E2E_DEV_SERVER=1 yarn test-e2e-integrations",
"db-setup-tests": "dotenv -e ./test/.env.test -- yarn workspace @calcom/prisma prisma generate",
"playwright-report": "playwright show-report playwright/reports/playwright-html-report",
"test-codegen": "yarn playwright codegen http://localhost:3000",
"type-check": "tsc --pretty --noEmit",
"build": "next build",

View File

@ -1 +0,0 @@
test-results

View File

@ -1,68 +0,0 @@
import { devices, PlaywrightTestConfig } from "@playwright/test";
import dotenv from "dotenv";
import { addAliases } from "module-alias";
import * as path from "path";
dotenv.config({ path: "./env" });
// 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 + "/apps/web/components",
"@lib": __dirname + "/apps/web/lib",
"@server": __dirname + "/apps/web/server",
"@ee": __dirname + "/apps/web/ee",
});
const outputDir = path.join(__dirname, "..", "test-results");
const testDir = path.join(__dirname, "..", "tests");
const DEFAULT_NAVIGATION_TIMEOUT = 600000;
const headless = !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS;
process.env.PLAYWRIGHT_TEST_BASE_URL = "http://localhost:3000";
const quickMode = process.env.QUICK === "true";
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: 0,
workers: quickMode ? 1 : 1,
timeout: 60_000,
maxFailures: headless ? 10 : undefined,
reporter: [
[process.env.CI ? "github" : "list"],
["html", { outputFolder: path.join(outputDir, "reports/playwright-html-report"), open: "never" }],
["junit", { outputFile: path.join(outputDir, "reports/results.xml") }],
],
outputDir,
use: {
baseURL: "http://localhost:3000/",
locale: "en-US",
trace: "retain-on-failure",
headless,
},
projects: [
{
name: "chromium",
testDir,
use: {
...devices["Desktop Chrome"],
/** If navigation takes more than this, then something's wrong, let's fail fast. */
navigationTimeout: DEFAULT_NAVIGATION_TIMEOUT,
},
},
/* {
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
}, */
],
};
export default config;

View File

@ -1,51 +0,0 @@
import { test as base } from "@playwright/test";
import { Server } from "http";
import { rest } from "msw";
import type { SetupServerApi } from "msw/node";
import { nextServer } from "../../playwright-integrations/next-server";
import { createBookingsFixture } from "../../playwright/fixtures/bookings";
import { createPaymentsFixture } from "../../playwright/fixtures/payments";
import { createUsersFixture } from "../../playwright/fixtures/users";
interface Fixtures {
users: ReturnType<typeof createUsersFixture>;
bookings: ReturnType<typeof createBookingsFixture>;
payments: ReturnType<typeof createPaymentsFixture>;
server: Server;
requestInterceptor: SetupServerApi;
rest: typeof rest;
}
/**
* @see https://playwright.dev/docs/test-fixtures
*/
export const test = base.extend<Fixtures>({
users: async ({ page }, use, workerInfo) => {
const usersFixture = createUsersFixture(page, workerInfo);
await use(usersFixture);
},
bookings: async ({ page }, use) => {
const bookingsFixture = createBookingsFixture(page);
await use(bookingsFixture);
},
payments: async ({ page }, use) => {
const payemntsFixture = createPaymentsFixture(page);
await use(payemntsFixture);
},
// This fixture runs for each worker, ensuring that every worker starts it's own Next.js instance on which we can attach MSW
// A single worker can run many tests
server: [
async ({}, use) => {
const server = await nextServer();
await use(server);
server.close();
},
{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
scope: "worker",
auto: true,
},
],
});

View File

@ -5,13 +5,15 @@ import { test } from "./lib/fixtures";
test.describe.configure({ mode: "parallel" });
test.describe("App Store - Authed", () => {
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("Browse apple-calendar and try to install", async ({ page }) => {
test("Browse apple-calendar and try to install", async ({ page, users }) => {
const pro = await users.create();
await pro.login();
await page.goto("/apps");
await page.click('[data-testid="app-store-category-calendar"]');
await page.click('[data-testid="app-store-app-card-apple-calendar"]');
await page.click('[data-testid="install-app-button"]');
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
await pro.delete();
});
});

View File

@ -0,0 +1,102 @@
import { expect } from "@playwright/test";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { test } from "../lib/fixtures";
import { todo } from "../lib/testUtils";
test.describe("Can signup from a team invite", async () => {
test.beforeEach(async ({ users }) => {
const proUser = await users.create();
await proUser.login();
});
test.afterEach(async ({ users }) => users.deleteAll());
test("Team invites validations work and can accept invite", async ({ browser, page, users, prisma }) => {
const [proUser] = users.get();
const teamName = `${proUser.username}'s Team`;
const testUser = {
username: `${proUser.username}-member`,
password: `${proUser.username}-member`,
email: `${proUser.username}-member@example.com`,
};
await page.goto("/settings/teams");
// Create a new team
await page.click("text=New Team");
await page.fill('input[id="name"]', teamName);
await page.click('[data-testid="create-new-team-button"]');
// Go to new team page
await page.click(`a[title="${teamName}"]`);
// Add new member 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.verificationToken.findFirstOrThrow({
where: { identifier: testUser.email },
select: { token: true },
});
if (!proUser.username) throw Error("Test username is null, can't continue");
// Open a new user window to accept the invite
const newPage = await browser.newPage();
await newPage.goto(`/auth/signup?token=${tokenObj.token}&callbackUrl=${WEBAPP_URL}/settings/teams`);
// Fill in form
await newPage.fill('input[name="username"]', proUser.username); // Invalid username
await newPage.fill('input[name="email"]', testUser.email);
await newPage.fill('input[name="password"]', testUser.password);
await newPage.fill('input[name="passwordcheck"]', testUser.password);
await newPage.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit
await expect(newPage.locator('text="Username already taken"')).toBeVisible();
// Email address is already registered
// TODO: Form errors don't disappear when corrected and resubmitted, so we need to refresh
await newPage.reload();
await newPage.fill('input[name="username"]', testUser.username);
await newPage.fill('input[name="email"]', `${proUser.username}@example.com`); // Taken email
await newPage.fill('input[name="password"]', testUser.password);
await newPage.fill('input[name="passwordcheck"]', testUser.password);
await newPage.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit
await expect(newPage.locator('text="Email address is already registered"')).toBeVisible();
// Successful signup
// TODO: Form errors don't disappear when corrected and resubmitted, so we need to refresh
await newPage.reload();
await newPage.fill('input[name="username"]', testUser.username);
await newPage.fill('input[name="email"]', testUser.email);
await newPage.fill('input[name="password"]', testUser.password);
await newPage.fill('input[name="passwordcheck"]', testUser.password);
await newPage.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit
await expect(newPage.locator(`[data-testid="login-form"]`)).toBeVisible();
// We don't need the new browser anymore
await newPage.close();
const createdUser = await prisma.user.findUniqueOrThrow({
where: { email: testUser.email },
include: { teams: { include: { team: true } } },
});
console.log("createdUser", createdUser);
// Check that the user was created
expect(createdUser).not.toBeNull();
expect(createdUser.username).toBe(testUser.username);
expect(createdUser.password).not.toBeNull();
expect(createdUser.emailVerified).not.toBeNull();
// Check that the user accepted the team invite
expect(createdUser.teams).toHaveLength(1);
expect(createdUser.teams[0].team.name).toBe(teamName);
expect(createdUser.teams[0].role).toBe("MEMBER");
expect(createdUser.teams[0].accepted).toBe(true);
});
});
todo("Can login using 2FA");

View File

@ -1,120 +0,0 @@
import { expect, test } from "@playwright/test";
import prisma from "@calcom/prisma";
import { WEBAPP_URL } from "@lib/config/constants";
import { todo } from "../lib/testUtils";
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.verificationToken.findFirst({
where: { identifier: testUser.email },
select: { token: true },
});
token = tokenObj?.token;
signupFromInviteURL = `/auth/signup?token=${token}&callbackUrl=${WEBAPP_URL}/settings/teams`;
});
test.afterAll(async () => {
// Delete test user
await prisma.user.delete({
where: { email: testUser.email },
});
// Delete verification request
await prisma.verificationToken.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 },
include: {
teams: {
include: {
team: true,
},
},
},
});
// Check that the user was created
expect(createdUser).not.toBeNull();
expect(createdUser?.username).toBe(testUser.validUsername);
expect(createdUser?.password).not.toBeNull();
expect(createdUser?.emailVerified).not.toBeNull();
// Check that the user accepted the team invite
expect(createdUser?.teams).toHaveLength(1);
expect(createdUser?.teams[0].team.name).toBe(team.name);
expect(createdUser?.teams[0].team.slug).toBe(team.slug);
expect(createdUser?.teams[0].role).toBe("MEMBER");
expect(createdUser?.teams[0].accepted).toBe(true);
});
});
todo("Can login using 2FA");

View File

@ -0,0 +1,23 @@
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
test.afterEach(({ users }) => users.deleteAll());
test.describe("Change Passsword Test", () => {
test("change password", async ({ page, users }) => {
const pro = await users.create();
await pro.login();
// Go to http://localhost:3000/settings/security
await page.goto("/settings/security");
if (!pro.username) throw Error("Test user doesn't have a username");
// Fill form
await page.fill('[name="current_password"]', pro.username);
await page.fill('[name="new_password"]', `${pro.username}1111`);
await page.press('[name="new_password"]', "Enter");
await expect(page.locator(`text=Your password has been successfully changed.`)).toBeVisible();
});
});

View File

@ -1,30 +0,0 @@
import { expect, test } from "@playwright/test";
test.describe("Change Passsword Test", () => {
// Using logged in state from globalSteup
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("change password", async ({ page }) => {
// Try to go homepage
await page.goto("/");
// It should redirect you to the event-types page
await page.waitForSelector("[data-testid=event-types]");
// Go to http://localhost:3000/settings/security
await page.goto("/settings/security");
// Fill form
await page.fill('[name="current_password"]', "pro");
await page.fill('[name="new_password"]', "pro1");
await page.press('[name="new_password"]', "Enter");
await expect(page.locator(`text=Your password has been successfully changed.`)).toBeVisible();
// Let's revert back to prevent errors on other tests
await page.fill('[name="current_password"]', "pro1");
await page.fill('[name="new_password"]', "pro");
await page.press('[name="new_password"]', "Enter");
await expect(page.locator(`text=Your password has been successfully changed.`)).toBeVisible();
});
});

View File

@ -5,7 +5,6 @@ import { getFreePlanPrice, getProPlanPrice } from "@calcom/app-store/stripepayme
import dayjs from "@calcom/dayjs";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { WEBAPP_URL } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import { test } from "./lib/fixtures";
@ -26,7 +25,7 @@ test.describe("Change username on settings", () => {
await users.deleteAll();
});
test("User can change username", async ({ page, users }) => {
test("User can change username", async ({ page, users, prisma }) => {
const user = await users.create({ plan: UserPlan.TRIAL });
await user.login();
@ -45,12 +44,12 @@ test.describe("Change username on settings", () => {
page.click("[data-testid=save-username]"),
]);
const newUpdatedUser = await prisma.user.findFirst({
const newUpdatedUser = await prisma.user.findUniqueOrThrow({
where: {
id: user.id,
},
});
expect(newUpdatedUser?.username).toBe("demousernamex");
expect(newUpdatedUser.username).toBe("demousernamex");
});
test("User trial can update to PREMIUM username", async ({ page, users }, testInfo) => {

View File

@ -11,7 +11,6 @@ import {
test.describe.configure({ mode: "parallel" });
test.describe("dynamic booking", () => {
test.skip(true, "TODO: Re-enable after v1.7 launch");
test.beforeEach(async ({ page, users }) => {
const pro = await users.create();
await pro.login();
@ -19,7 +18,7 @@ test.describe("dynamic booking", () => {
await page.goto(`/${pro.username}+${free.username}`);
});
test.afterEach(async ({ page, users }) => {
test.afterEach(async ({ users }) => {
await users.deleteAll();
});

View File

@ -1,4 +1,6 @@
import { expect, Page, test } from "@playwright/test";
import { expect, Page } from "@playwright/test";
import { test } from "./lib/fixtures";
function chooseEmbedType(page: Page, embedType: string) {
page.locator(`[data-testid=${embedType}]`).click();
@ -86,14 +88,18 @@ async function expectToContainValidPreviewIframe(
test.describe.configure({ mode: "parallel" });
test.describe("Embed Code Generator Tests", () => {
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test.beforeEach(async ({ users }) => {
const pro = await users.create();
await pro.login();
});
test.describe("Event Types Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/event-types");
});
test("open Embed Dialog and choose Inline for First Event Type", async ({ page }) => {
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
@ -112,10 +118,15 @@ test.describe("Embed Code Generator Tests", () => {
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, { embedType: "inline", calLink: "pro/30min" });
await expectToContainValidPreviewIframe(page, {
embedType: "inline",
calLink: `${pro.username}/30-min`,
});
});
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page }) => {
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
@ -133,10 +144,14 @@ test.describe("Embed Code Generator Tests", () => {
await expectToContainValidCode(page, { embedType: "floating-popup" });
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, { embedType: "floating-popup", calLink: "pro/30min" });
await expectToContainValidPreviewIframe(page, {
embedType: "floating-popup",
calLink: `${pro.username}/30-min`,
});
});
test("open Embed Dialog and choose element-click for First Event Type", async ({ page }) => {
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
@ -154,7 +169,10 @@ test.describe("Embed Code Generator Tests", () => {
await expectToContainValidCode(page, { embedType: "element-click" });
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, { embedType: "element-click", calLink: "pro/30min" });
await expectToContainValidPreviewIframe(page, {
embedType: "element-click",
calLink: `${pro.username}/30-min`,
});
});
});

View File

@ -1,25 +1,23 @@
import { expect, Locator, test } from "@playwright/test";
import { expect } from "@playwright/test";
import { randomString } from "../lib/random";
import { deleteEventTypeByTitle } from "./lib/teardown";
import { test } from "./lib/fixtures";
test.describe.configure({ mode: "parallel" });
test.describe("Event Types tests", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
test.describe("pro user", () => {
let isCreated: Locator;
let eventTitle: string;
test.afterAll(async () => {
if (isCreated) await deleteEventTypeByTitle(eventTitle);
test.beforeEach(async ({ page, users }) => {
const proUser = await users.create();
await proUser.login();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
test.afterEach(async ({ users }) => {
await users.deleteAll();
});
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("has at least 2 events", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > *");
@ -34,7 +32,7 @@ test.describe("Event Types tests", () => {
test("can add new event type", async ({ page }) => {
await page.click("[data-testid=new-event-type]");
const nonce = randomString(3);
eventTitle = `hello ${nonce}`;
const eventTitle = `hello ${nonce}`;
await page.fill("[name=title]", eventTitle);
await page.fill("[name=length]", "10");
@ -47,15 +45,13 @@ test.describe("Event Types tests", () => {
});
await page.goto("/event-types");
isCreated = page.locator(`text='${eventTitle}'`);
await expect(isCreated).toBeVisible();
await expect(page.locator(`text='${eventTitle}'`)).toBeVisible();
});
test("enabling recurring event comes with default options", async ({ page }) => {
await page.click("[data-testid=new-event-type]");
const nonce = randomString(3);
eventTitle = `my recurring event ${nonce}`;
const eventTitle = `my recurring event ${nonce}`;
await page.fill("[name=title]", eventTitle);
await page.fill("[name=length]", "15");
@ -70,8 +66,7 @@ test.describe("Event Types tests", () => {
await page.click("[data-testid=show-advanced-settings]");
await expect(page.locator("[data-testid=recurring-event-collapsible]")).not.toBeVisible();
await page.click("[data-testid=recurring-event-check]");
isCreated = page.locator("[data-testid=recurring-event-collapsible]");
await expect(isCreated).toBeVisible();
await expect(page.locator("[data-testid=recurring-event-collapsible]")).toBeVisible();
expect(
await page
@ -91,8 +86,12 @@ test.describe("Event Types tests", () => {
});
test("can duplicate an existing event type", async ({ page }) => {
// TODO: Locate the actual EventType available in list. This ID might change in future
const eventTypeId = "6";
const firstElement = await page.waitForSelector(
'[data-testid="event-types"] a[href^="/event-types/"] >> nth=0'
);
const href = await firstElement.getAttribute("href");
if (!href) throw new Error("No href found for event type");
const [eventTypeId] = href.split("/").reverse();
const firstTitle = await page.locator(`[data-testid=event-type-title-${eventTypeId}]`).innerText();
const firstFullSlug = await page.locator(`[data-testid=event-type-slug-${eventTypeId}]`).innerText();
const firstSlug = firstFullSlug.split("/")[2];
@ -134,7 +133,13 @@ test.describe("Event Types tests", () => {
});
test.describe("free user", () => {
test.use({ storageState: "playwright/artifacts/freeStorageState.json" });
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
await free.login();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
test("has at least 2 events where first is enabled", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > *");

View File

@ -0,0 +1,81 @@
import type { Page } from "@playwright/test";
export const createEmbedsFixture = (page: Page) => {
return async (calNamespace: string) => {
await page.addInitScript(
({ calNamespace }: { calNamespace: string }) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.eventsFiredStoreForPlaywright = window.eventsFiredStoreForPlaywright || {};
document.addEventListener("DOMContentLoaded", function tryAddingListener() {
if (parent !== window) {
// Firefox seems to execute this snippet for iframe as well. Avoid that. It must be executed only for parent frame.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyDisplay = document.body.style.display;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyBackground = document.body.style.background;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialValuesSet = true;
return;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
let api = window.Cal;
if (!api) {
setTimeout(tryAddingListener, 500);
return;
}
if (calNamespace) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
api = window.Cal.ns[calNamespace];
}
console.log("PlaywrightTest:", "Adding listener for __iframeReady");
if (!api) {
throw new Error(`namespace "${calNamespace}" not found`);
}
api("on", {
action: "*",
callback: (e: any) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.iframeReady = true; // Technically if there are multiple cal embeds, it can be set due to some other iframe. But it works for now. Improve it when it doesn't work
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const store = window.eventsFiredStoreForPlaywright;
const eventStore = (store[`${e.detail.type}-${e.detail.namespace}`] =
store[`${e.detail.type}-${e.detail.namespace}`] || []);
eventStore.push(e.detail);
},
});
});
},
{ calNamespace }
);
};
};
export const createGetActionFiredDetails = (page: Page) => {
return async ({ calNamespace, actionType }: { calNamespace: string; actionType: string }) => {
if (!page.isClosed()) {
return await page.evaluate(
({ actionType, calNamespace }) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
return window.eventsFiredStoreForPlaywright[`${actionType}-${calNamespace}`];
},
{ actionType, calNamespace }
);
}
};
};

View File

@ -0,0 +1,33 @@
import type { Server } from "http";
import { nextServer } from "../lib/next-server";
type ServerFixture = ReturnType<typeof createServerFixture>;
// creates a servers fixture instance and stores the collection
export const createServersFixture = () => {
const store = { servers: [] } as { servers: ServerFixture[] };
return {
create: async () => {
const server = await nextServer();
const serverFixture = createServerFixture(server);
store.servers.push(serverFixture);
return serverFixture;
},
get: () => store.servers,
deleteAll: async () => {
store.servers.forEach((server) => server.delete());
store.servers = [];
},
};
};
// creates the single server fixture
const createServerFixture = (server: Server) => {
const store = { server };
return {
self: async () => store.server,
delete: async () => store.server.close(),
};
};

View File

@ -71,7 +71,7 @@ export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
},
get: () => store.users,
logout: async () => {
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/auth/logout`);
await page.goto("/auth/logout");
},
deleteAll: async () => {
const ids = store.users.map((u) => u.id);
@ -166,8 +166,7 @@ export async function login(
const signInLocator = loginLocator.locator('[type="submit"]');
//login
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await page.goto(process.env.PLAYWRIGHT_TEST_BASE_URL!);
await page.goto("/");
await emailLocator.fill(user.email ?? `${user.username}@example.com`);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await passwordLocator.fill(user.password ?? user.username!);

View File

@ -4,14 +4,9 @@ import { setupServer } from "msw/node";
import { v4 as uuidv4 } from "uuid";
import { prisma } from "@calcom/prisma";
import {
createHttpServer,
selectFirstAvailableTimeSlotNextMonth,
todo,
waitFor,
} from "@calcom/web/playwright/lib/testUtils";
import { test } from "../lib/fixtures";
import { test } from "./lib/fixtures";
import { createHttpServer, selectFirstAvailableTimeSlotNextMonth, todo, waitFor } from "./lib/testUtils";
declare let global: {
E2E_EMAILS?: ({ text: string } | Record<string, unknown>)[];
@ -23,11 +18,6 @@ const requestInterceptor = setupServer(
return res(ctx.status(200));
})
);
requestInterceptor.listen({
// Comment this to log which all requests are going that are unmocked
onUnhandledRequest: "bypass",
});
requestInterceptor.use();
const addOauthBasedIntegration = async function ({
page,
@ -76,7 +66,7 @@ const addOauthBasedIntegration = async function ({
})
);
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/apps/${slug}`);
await page.goto(`/apps/${slug}`);
await page.click('[data-testid="install-app-button"]');
};
@ -113,7 +103,7 @@ async function bookEvent(page: Page, calLink: string) {
// It would also allow correct snapshot to be taken for current month.
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/${calLink}`);
await page.goto(`/${calLink}`);
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).click();
page.locator('[data-testid="time"]').nth(0).click();
@ -155,7 +145,22 @@ async function bookEvent(page: Page, calLink: string) {
test.describe.configure({ mode: "parallel" });
test.describe("Integrations", () => {
// Enable API mocking before tests.
test.beforeAll(() =>
requestInterceptor.listen({
// Comment this to log which all requests are going that are unmocked
onUnhandledRequest: "bypass",
})
);
// Reset any runtime request handlers we may add during the tests.
test.afterEach(() => requestInterceptor.resetHandlers());
// Disable API mocking after the tests are done.
test.afterAll(() => requestInterceptor.close());
// TODO: Fix MSW mocking
test.fixme("Integrations", () => {
test.beforeEach(() => {
global.E2E_EMAILS = [];
});
@ -365,7 +370,7 @@ test.describe("Integrations", () => {
const user = await users.create();
const [eventType] = user.eventTypes;
await user.login();
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/settings/developer`);
await page.goto(`/settings/developer`);
// --- add webhook
await page.click('[data-testid="new_webhook"]');
@ -384,7 +389,7 @@ test.describe("Integrations", () => {
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
// --- Book the first available day next month in the pro user's "30min"-event
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/${user.username}/${eventType.slug}`);
await page.goto(`/${user.username}/${eventType.slug}`);
await selectFirstAvailableTimeSlotNextMonth(page);
// --- fill form

View File

@ -1,13 +1,21 @@
import { test as base } from "@playwright/test";
import prisma from "@calcom/prisma";
import { createBookingsFixture } from "../fixtures/bookings";
import { createEmbedsFixture, createGetActionFiredDetails } from "../fixtures/embeds";
import { createPaymentsFixture } from "../fixtures/payments";
import { createServersFixture } from "../fixtures/servers";
import { createUsersFixture } from "../fixtures/users";
interface Fixtures {
users: ReturnType<typeof createUsersFixture>;
bookings: ReturnType<typeof createBookingsFixture>;
payments: ReturnType<typeof createPaymentsFixture>;
addEmbedListeners: ReturnType<typeof createEmbedsFixture>;
getActionFiredDetails: ReturnType<typeof createGetActionFiredDetails>;
servers: ReturnType<typeof createServersFixture>;
prisma: typeof prisma;
}
/**
@ -26,4 +34,19 @@ export const test = base.extend<Fixtures>({
const payemntsFixture = createPaymentsFixture(page);
await use(payemntsFixture);
},
addEmbedListeners: async ({ page }, use) => {
const embedsFixture = createEmbedsFixture(page);
await use(embedsFixture);
},
getActionFiredDetails: async ({ page }, use) => {
const getActionFiredDetailsFixture = createGetActionFiredDetails(page);
await use(getActionFiredDetailsFixture);
},
servers: async ({}, use) => {
const servers = createServersFixture();
await use(servers);
},
prisma: async ({}, use) => {
await use(prisma);
},
});

View File

@ -6,7 +6,7 @@ test("Should display Google Login button", async ({ page }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_GOOGLE_LOGIN_ENABLED, "It should only run if Google Login is installed");
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/auth/login`);
await page.goto(`/auth/login`);
await expect(page.locator(`[data-testid=google]`)).toBeVisible();
});
@ -15,7 +15,7 @@ test("Should display SAML Login button", async ({ page }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_SAML_LOGIN_ENABLED, "It should only run if SAML Login is installed");
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/auth/login`);
await page.goto(`/auth/login`);
await expect(page.locator(`[data-testid=saml]`)).toBeVisible();
});

View File

@ -1,23 +1,15 @@
import { expect, test } from "@playwright/test";
import { expect } from "@playwright/test";
import prisma from "@calcom/prisma";
test.describe("Onboarding", () => {
test.use({ storageState: "playwright/artifacts/onboardingStorageState.json" });
import { test } from "./lib/fixtures";
// You want to always reset account completedOnboarding after each test
test.afterEach(async () => {
// Revert DB change
await prisma.user.update({
where: {
email: "onboarding@example.com",
},
data: {
username: "onboarding",
completedOnboarding: false,
},
});
test.describe("Onboarding", () => {
test.beforeEach(async ({ users }) => {
const onboardingUser = await users.create({ completedOnboarding: false });
await onboardingUser.login();
});
test.afterEach(({ users }) => users.deleteAll());
test("redirects to /getting-started after login", async ({ page }) => {
await page.goto("/event-types");
@ -29,10 +21,13 @@ test.describe("Onboarding", () => {
});
test.describe("Onboarding", () => {
test("update onboarding username via localstorage", async ({ page }) => {
test("update onboarding username via localstorage", async ({ page, users }) => {
const [onboardingUser] = users.get();
/**
* TODO:
* We need to come up with a better test since all test are run in an incognito window.
* Meaning that all localstorage access is null here.
* Let's try saving the desiredUsername in the metadata instead
*/
test.fixme();
await page.addInitScript(() => {
@ -46,7 +41,7 @@ test.describe("Onboarding", () => {
await page.waitForTimeout(1000);
const updatedUser = await prisma.user.findUnique({
where: { email: "onboarding@example.com" },
where: { id: onboardingUser.id },
select: { id: true, username: true },
});

View File

@ -1,12 +1,12 @@
import { test } from "@playwright/test";
import { IS_SAML_LOGIN_ENABLED } from "../server/lib/constants";
import { login } from "./fixtures/users";
import { test } from "./lib/fixtures";
test.describe("SAML tests", () => {
// Using logged in state from globalSteup
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("test SAML configuration UI with pro@example.com", async ({ page }) => {
// TODO: Figure out a way to use the users from fixtures here, right now we cannot set
// the SAML_ADMINS env variables dynamically
await login({ username: "pro", email: "pro@example.com", password: "pro" }, page);
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_SAML_LOGIN_ENABLED, "It should only run if SAML is enabled");
// Try to go Security page

View File

@ -50,6 +50,22 @@ const config: Config = {
testEnvironment: "jsdom",
setupFiles: ["<rootDir>/packages/app-store/closecomothercalendar/test/globals.ts"],
},
{
displayName: "@calcom/api",
roots: ["<rootDir>/apps/api"],
testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"],
transform: {
"^.+\\.ts?$": "ts-jest",
},
transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
testEnvironment: "node",
clearMocks: true,
moduleNameMapper: {
"^@lib/(.*)$": "<rootDir>/apps/api/lib/$1",
"^@api/(.*)$": "<rootDir>/apps/api/pages/api/$1",
},
// setupFilesAfterEnv: ["<rootDir>/apps/api/jest.setup.ts"], // Uncomment when API becomes public
},
],
watchPlugins: [
"jest-watch-typeahead/filename",

View File

@ -11,12 +11,11 @@
"packages/app-store/ee/*"
],
"scripts": {
"app-e2e-quick": "turbo run app-e2e-quick",
"app-store-cli": "yarn workspace @calcom/app-store-cli",
"app-store:build": "yarn app-store-cli build",
"app-store:watch": "yarn app-store-cli watch",
"app-store": "yarn app-store-cli cli",
"build": "turbo run build --scope=\"@calcom/web\" --include-dependencies",
"build": "turbo run build --filter=@calcom/web...",
"clean": "find . -name node_modules -o -name .next -o -name .turbo -o -name dist -type d -prune | xargs rm -rf",
"db-deploy": "turbo run db-deploy",
"db-seed": "turbo run db-seed",
@ -49,12 +48,13 @@
"prisma": "yarn workspace @calcom/prisma prisma",
"start": "turbo run start --scope=\"@calcom/web\"",
"tdd": "jest --watch",
"test-e2e": "turbo run test --scope=\"@calcom/web\" && yarn turbo run test-e2e --scope=\"@calcom/web\" --concurrency=1",
"e2e": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --project=@calcom/web",
"e2e:app-store": "QUICK=true yarn playwright test --project=@calcom/app-store",
"test-e2e": "yarn db-seed && yarn build && yarn e2e",
"test-e2e:app-store": "yarn db-seed && yarn build && yarn e2e:app-store",
"test-playwright": "yarn playwright test --config=tests/config/playwright.config.ts",
"test": "jest",
"turbo-w": "node turbo-wrapper.js",
"type-check": "turbo run type-check",
"test-e2e-integrations": "turbo run test-e2e-integrations --scope=\"@calcom/web\" --concurrency=1",
"web": "yarn workspace @calcom/web"
},
"devDependencies": {

View File

@ -1,19 +0,0 @@
import { Page } from "@playwright/test";
export async function loginAsUser(username: string, page: Page) {
// Skip if file exists
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/auth/login`);
// Click input[name="email"]
await page.click('input[name="email"]');
// Fill input[name="email"]
await page.fill('input[name="email"]', `${username}@example.com`);
// Press Tab
await page.press('input[name="email"]', "Tab");
// Fill input[name="password"]
await page.fill('input[name="password"]', username);
// Press Enter
await page.press('input[name="password"]', "Enter");
await page.waitForSelector("[data-testid=dashboard-shell]");
// Save signed-in state to '${username}StorageState.json'.
await page.context().storageState({ path: `playwright/artifacts/${username}StorageState.json` });
}

View File

@ -1,57 +0,0 @@
import { PlaywrightTestConfig, devices } from "@playwright/test";
import { config as dotEnvConfig } from "dotenv";
import * as path from "path";
// TODO: May be derive it automatically, so that moving the file to another location doesn't require changing the code
dotEnvConfig({ path: "../../../../../.env" });
const DEFAULT_NAVIGATION_TIMEOUT = 15000;
// Paths are relative to main playwright config.
const outputDir = path.join("../results");
const testDir = path.join("../tests");
// Quick Mode has no retries to fail fast and quickly re-iterate
// Also, it runs the tests only one browser for the same reason
const quickMode = process.env.QUICK === "true";
const CI = process.env.CI;
export const config: PlaywrightTestConfig = {
forbidOnly: !!CI,
retries: quickMode && !CI ? 0 : 1,
workers: 1,
timeout: 60_000,
reporter: [
[CI ? "github" : "list"],
[
"html",
{
outputFolder: path.join(process.cwd(), "playwright", "reports", "playwright-html-report"),
open: "never",
},
],
["junit", { outputFile: path.join(process.cwd(), "playwright", "reports", "results.xml") }],
],
outputDir,
webServer: {
command: "NEXT_PUBLIC_IS_E2E=1 yarn workspace @calcom/web start -p 3000",
port: 3000,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: "http://localhost:3000",
locale: "en-US",
trace: "retain-on-failure",
headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS,
},
projects: [
{
name: "chromium",
testDir,
use: {
...devices["Desktop Chrome"],
/** If navigation takes more than this, then something's wrong, let's fail fast. */
navigationTimeout: DEFAULT_NAVIGATION_TIMEOUT,
},
},
],
};

View File

@ -1 +0,0 @@
export * from "../../../../apps/web/playwright/lib/testUtils";

View File

@ -5,7 +5,7 @@ import {
getCloseComCustomActivityTypeFieldsIds,
getCloseComLeadId,
} from "@calcom/lib/CloseComeUtils";
import { CalendarEvent } from "@calcom/types/Calendar";
import type { CalendarEvent } from "@calcom/types/Calendar";
jest.mock("@calcom/lib/CloseCom", () => ({
default: class {

View File

@ -5,10 +5,6 @@
"version": "0.0.0",
"main": "./index.ts",
"description": "It would allow a booker to connect with the right person or choose the right event, faster. It would work by taking inputs from the booker and using that data to route to the correct booker/event as configured by Cal user ",
"scripts": {
"app-e2e": "yarn playwright test --config=playwright/config/playwright.config.ts",
"app-e2e-quick": "QUICK=true yarn app-e2e"
},
"dependencies": {
"@calcom/lib": "*",
"dotenv": "^16.0.1",

View File

@ -1,44 +0,0 @@
import { Page, chromium } from "@playwright/test";
// TODO: Import it in _playwright/config/globalSetup.ts and export it from there.
import { loginAsUser } from "@calcom/app-store/_apps-playwright/config/globalSetup";
import { hashPassword } from "@calcom/lib/auth";
import prisma from "@calcom/prisma";
async function installApp(appName: string, redirectUrl: string, page: Page) {
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/apps/${appName}`);
await page.click('[data-testid="install-app-button"]');
await page.waitForNavigation({
url: (url) => {
return url.pathname == redirectUrl;
},
});
}
async function createUser(userName: string) {
const email = `${userName}@example.com`;
await prisma.user.create({
data: {
username: userName,
email,
completedOnboarding: true,
password: await hashPassword(userName),
},
});
}
async function globalSetup(/* config: FullConfig */) {
const browser = await chromium.launch({
headless: true,
});
const page = await browser.newPage();
const appName = "routing_forms";
const userName = `${appName}-e2e-${Math.random()}`;
process.env.APP_USER_NAME = userName;
await createUser(userName);
await loginAsUser(userName, page);
await installApp(appName, `/apps/${appName}/forms`, page);
page.context().close();
}
export default globalSetup;

View File

@ -1,18 +0,0 @@
import prisma from "@calcom/prisma";
async function deleteUser(userName: string) {
await prisma.user.deleteMany({
where: {
AND: {
username: {
contains: userName,
},
},
},
});
}
async function globalTeardown(/* config: FullConfig */) {
await deleteUser("routing_forms-e2e");
}
export default globalTeardown;

View File

@ -1,12 +0,0 @@
import { expect, Config } from "@playwright/test";
import { config as baseConfig } from "@calcom/app-store/_apps-playwright/config/playwright.config";
const config: Config = {
...baseConfig,
globalSetup: require.resolve("./globalSetup"),
globalTeardown: require.resolve("./globalTeardown"),
};
expect.extend({});
export default config;

View File

@ -1,5 +0,0 @@
import { test as base } from "@playwright/test";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Fixtures {}
export const test = base.extend<Fixtures>({});

View File

@ -1,20 +0,0 @@
import prisma from "@calcom/prisma";
export * from "@calcom/app-store/_apps-playwright/lib/testUtils";
export async function cleanUpForms() {
await prisma.app_RoutingForms_Form.deleteMany({
where: {
user: {
username: process.env.APP_USER_NAME,
},
},
});
}
export async function cleanUpSeededForm(formId: string) {
return await prisma.app_RoutingForms_FormResponse.deleteMany({
where: {
formId,
},
});
}

View File

@ -1,9 +1,7 @@
import { expect, Page } from "@playwright/test";
import { seededForm } from "@calcom/prisma/seed-app-store";
import { test } from "../fixtures/fixtures";
import { cleanUpForms, cleanUpSeededForm } from "../lib/testUtils";
import { test } from "@calcom/web/playwright/lib/fixtures";
async function gotoRoutingLink(page: Page, formId: string) {
await page.goto(`/forms/${formId}`);
@ -69,12 +67,24 @@ async function fillForm(
};
}
test.use({ storageState: `playwright/artifacts/${process.env.APP_USER_NAME}StorageState.json` });
test.describe("Routing Forms", () => {
test("should be able to add a new form and view it", async ({ page }) => {
page.goto("/");
test.beforeEach(async ({ page, users }) => {
const user = await users.create({ username: "routing_forms" });
await user.login();
// Install app
await page.goto(`/apps/routing_forms`);
await page.click('[data-testid="install-app-button"]');
await page.waitForNavigation({
url: (url) => url.pathname === `/apps/routing_forms/forms`,
});
});
await page.click('[href="/apps/routing_forms/forms"]');
test.afterEach(async ({ users }) => {
// This also delete forms on cascade
await users.deleteAll();
});
test("should be able to add a new form and view it", async ({ page }) => {
await page.waitForSelector('[data-testid="empty-screen"]');
const formId = await addForm(page);
@ -98,8 +108,6 @@ test.describe("Routing Forms", () => {
});
test("should be able to edit the form", async ({ page }) => {
await page.goto("/apps/routing_forms/forms");
await addForm(page);
const description = "Test Description";
@ -218,12 +226,4 @@ test.describe("Routing Forms", () => {
expect(await page.locator('button[type="submit"][disabled]').count()).toBe(0);
});
});
test.afterAll(() => {
cleanUpForms();
});
test.afterEach(() => {
cleanUpSeededForm(seededForm.id);
});
});

View File

@ -1,7 +1,8 @@
import { expect, Frame } from "@playwright/test";
import { expect } from "@playwright/test";
import { test } from "../fixtures/fixtures";
import { todo, getEmbedIframe, bookFirstEvent, deleteAllBookingsByEmail } from "../lib/testUtils";
import { test } from "@calcom/web/playwright/lib/fixtures";
import { bookFirstEvent, deleteAllBookingsByEmail, getEmbedIframe, todo } from "../lib/testUtils";
test("Inline Iframe - Configured with Dark Theme", async ({
page,

View File

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { test } from "../fixtures/fixtures";
import { test } from "@calcom/web/playwright/lib/fixtures";
test("Preview - embed-core should load", async ({ page }) => {
await page.goto("http://localhost:3000/embed/preview.html");

View File

@ -1,4 +1,4 @@
import { CalendarEvent } from "@calcom/types/Calendar";
import type { CalendarEvent } from "@calcom/types/Calendar";
import CloseCom, {
CloseComCustomActivityCreate,

96
playwright.config.ts Normal file
View File

@ -0,0 +1,96 @@
import { devices, PlaywrightTestConfig } from "@playwright/test";
import dotEnv from "dotenv";
import * as os from "os";
import * as path from "path";
dotEnv.config({ path: ".env" });
const outputDir = path.join(__dirname, "test-results");
const testDir = path.join(__dirname, "apps/web/playwright");
const DEFAULT_NAVIGATION_TIMEOUT = 15000;
const headless = !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS;
const IS_EMBED_TEST = process.argv.some((a) => a.startsWith("--project=@calcom/embed-core"));
const webServer: PlaywrightTestConfig["webServer"] = [
{
command: "NEXT_PUBLIC_IS_E2E=1 yarn workspace @calcom/web start -p 3000",
port: 3000,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
},
];
if (IS_EMBED_TEST) {
webServer.push({
command: "yarn workspace @calcom/embed-core run-p 'embed-dev' 'embed-web-start'",
port: 3100,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
});
}
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: 1,
workers: os.cpus().length,
timeout: 60_000,
maxFailures: headless ? 10 : undefined,
reporter: [
[process.env.CI ? "github" : "list"],
["html", { outputFolder: "./test-results/reports/playwright-html-report", open: "never" }],
["junit", { outputFile: "./test-results/reports/results.xml" }],
],
outputDir: path.join(outputDir, "results"),
webServer,
use: {
baseURL: "http://localhost:3000/",
locale: "en-US",
trace: "retain-on-failure",
headless,
},
projects: [
{
name: "@calcom/web",
testDir: "./apps/web/playwright",
testMatch: /.*\.e2e\.tsx?/,
use: {
...devices["Desktop Chrome"],
/** If navigation takes more than this, then something's wrong, let's fail fast. */
navigationTimeout: DEFAULT_NAVIGATION_TIMEOUT,
},
},
{
name: "@calcom/app-store",
testDir: "./packages/app-store/",
testMatch: /.*\.e2e\.tsx?/,
use: {
...devices["Desktop Chrome"],
/** If navigation takes more than this, then something's wrong, let's fail fast. */
navigationTimeout: DEFAULT_NAVIGATION_TIMEOUT,
},
},
{
name: "@calcom/embed-core",
testDir: "./packages/embeds/",
testMatch: /.*\.e2e\.tsx?/,
use: { ...devices["Desktop Chrome"] },
},
{
name: "@calcom/embed-core--firefox",
testDir: "./packages/embeds/",
testMatch: /.*\.e2e\.tsx?/,
use: { ...devices["Desktop Firefox"] },
},
{
name: "@calcom/embed-core--webkit",
testDir: "./packages/embeds/",
testMatch: /.*\.e2e\.tsx?/,
use: { ...devices["Desktop Safari"] },
},
],
};
export default config;

View File

@ -1,48 +0,0 @@
import { loadEnvConfig } from "@next/env";
import { Browser, chromium } from "@playwright/test";
import fs from "fs";
export async function loginAsUser(username: string, browser: Browser) {
// Skip is file exists
if (fs.existsSync(`playwright/artifacts/${username}StorageState.json`)) return;
const page = await browser.newPage();
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/auth/login`);
// Click input[name="email"]
await page.click('input[name="email"]');
// Fill input[name="email"]
await page.fill('input[name="email"]', `${username}@example.com`);
// Press Tab
await page.press('input[name="email"]', "Tab");
// Fill input[name="password"]
await page.fill('input[name="password"]', username);
// Press Enter
await page.press('input[name="password"]', "Enter");
await page.waitForSelector(
username === "onboarding" ? "[data-testid=onboarding]" : "[data-testid=dashboard-shell]"
);
// Save signed-in state to '${username}StorageState.json'.
await page.context().storageState({ path: `playwright/artifacts/${username}StorageState.json` });
await page.context().close();
}
async function globalSetup(/* config: FullConfig */) {
loadEnvConfig(process.env.PWD);
const browser = await chromium.launch();
await loginAsUser("onboarding", browser);
// await loginAsUser("free-first-hidden", browser);
await loginAsUser("pro", browser);
await loginAsUser("trial", browser);
await loginAsUser("free", browser);
// await loginAsUser("usa", browser);
// await loginAsUser("teamfree", browser);
await loginAsUser("teampro", browser);
await browser.close();
// Clean up auth state after all tests are done
return () => {
const dir = `playwright/artifacts`;
fs.readdirSync(dir).forEach((f) => fs.rmSync(`${dir}/${f}`));
};
}
export default globalSetup;

View File

@ -1,61 +0,0 @@
import { devices, PlaywrightTestConfig } from "@playwright/test";
import dotEnv from "dotenv";
import * as os from "os";
import * as path from "path";
dotEnv.config({ path: "../../.env" });
const outputDir = path.join(__dirname, "..", "..", "test-results");
const testDir = path.join(__dirname, "..", "..", "apps/web/playwright");
const DEFAULT_NAVIGATION_TIMEOUT = 15000;
const headless = !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS;
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: 1,
workers: os.cpus().length,
timeout: 60_000,
maxFailures: headless ? 10 : undefined,
reporter: [
[process.env.CI ? "github" : "list"],
["html", { outputFolder: path.join(outputDir, "reports/playwright-html-report"), open: "never" }],
["junit", { outputFile: path.join(outputDir, "reports/results.xml") }],
],
globalSetup: require.resolve("./globalSetup"),
outputDir: path.join(outputDir, "results"),
webServer: {
command: "NEXT_PUBLIC_IS_E2E=1 yarn workspace @calcom/web start -p 3000",
port: 3000,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: "http://localhost:3000/",
locale: "en-US",
trace: "retain-on-failure",
headless,
},
projects: [
{
name: "chromium",
testDir,
use: {
...devices["Desktop Chrome"],
/** If navigation takes more than this, then something's wrong, let's fail fast. */
navigationTimeout: DEFAULT_NAVIGATION_TIMEOUT,
},
},
/* {
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
}, */
],
};
export default config;

View File

@ -1,6 +1,5 @@
{
"$schema": "https://turborepo.org/schema.json",
"baseBranch": "origin/main",
"pipeline": {
"@calcom/prisma#build": {
"cache": false,
@ -123,24 +122,6 @@
"embed-tests-quick": {
"cache": false
},
"test": {
"dependsOn": ["^test"]
},
"@calcom/web#db-setup-tests": {
"cache": false
},
"@calcom/web#test": {
"cache": false,
"dependsOn": ["@calcom/web#db-setup-tests"]
},
"test-e2e": {
"cache": false,
"dependsOn": ["@calcom/prisma#db-seed", "@calcom/web#build"]
},
"test-e2e-integrations": {
"cache": false,
"dependsOn": ["@calcom/prisma#db-seed", "@calcom/web#build"]
},
"type-check": {
"cache": false,
"outputs": []
@ -170,10 +151,6 @@
"^embed-tests-update-snapshots:ci"
]
},
"app-e2e-quick": {
"cache": false,
"dependsOn": ["@calcom/prisma#db-seed", "@calcom/web#build", "^app-e2e-quick"]
},
"//#env-check:common": {
"cache": false,
"inputs": ["./.env.example", "./.env"],
@ -183,6 +160,10 @@
"cache": false,
"inputs": ["./.env.appStore.example", "./.env.appStore"],
"outputs": ["./.env.appStore"]
},
"//#test": {
"cache": false,
"outputs": []
}
},
"globalDependencies": [

266
yarn.lock
View File

@ -2410,17 +2410,12 @@
resolved "https://registry.yarnpkg.com/@glidejs/glide/-/glide-3.5.2.tgz#7012c5920ecf202bbda44d8526fc979984b6dd54"
integrity sha512-7jGciNJ2bQ4eZLSNlSZ+VAyW63kALf420CvkEpK4lEsUfWJq9odqimci0YCiyNyMUFB+pWHwLYyNc57dijYsCg==
"@headlessui/react@^1.4.1":
version "1.6.6"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.6.tgz#3073c066b85535c9d28783da0a4d9288b5354d0c"
integrity sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==
"@headlessui/react@^1.5.0":
version "1.6.5"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.5.tgz#5587c537de809cf3146eb2ff263e5e940b1bf69c"
integrity sha512-3VkKteDxlxf3fE0KbfO9t60KC1lM7YNpZggLpwzVNg1J/zwL+h+4N7MBlFDVpInZI3rKlZGpNx0PWsG/9c2vQg==
"@heroicons/react@^1.0.4", "@heroicons/react@^1.0.6":
"@heroicons/react@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324"
integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==
@ -3770,17 +3765,6 @@
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-collection@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.0.tgz#0ec4c72fabd35a03b5787075ac799e3b17ca5710"
integrity sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-slot" "1.0.0"
"@radix-ui/react-compose-refs@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95"
@ -3830,13 +3814,6 @@
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-direction@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz#a2e0b552352459ecf96342c79949dd833c1e6e45"
integrity sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-dismissable-layer@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.5.tgz#9379032351e79028d472733a5cc8ba4a0ea43314"
@ -3984,7 +3961,7 @@
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-portal@1.0.0", "@radix-ui/react-portal@^1.0.0":
"@radix-ui/react-portal@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.0.tgz#7220b66743394fabb50c55cb32381395cc4a276b"
integrity sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==
@ -4058,22 +4035,6 @@
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-roving-focus@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.0.tgz#aadeb65d5dbcdbdd037078156ae1f57c2ff754ee"
integrity sha512-lHvO4MhvoWpeNbiJAoyDsEtbKqP2jkkdwsMVJ3kfqbkC71J/aXE6Th6gkZA1xHEqSku+t+UgoDjvE7Z3gsBpcg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-collection" "1.0.0"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-direction" "1.0.0"
"@radix-ui/react-id" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-select@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-0.1.1.tgz#ceedea6856a37e4079492e1c69601797cedd2c85"
@ -4099,7 +4060,7 @@
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-slider@^0.1.0", "@radix-ui/react-slider@^0.1.1":
"@radix-ui/react-slider@^0.1.1":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-0.1.4.tgz#a7b7a480ee00158195794b08cd3f1583cf102518"
integrity sha512-0z3bCcdrAi+FIcoLXS6r0ESVWuuyMnUJoCsFm7tC7Rtv95x34YtaI8YfSyQmzuMVS4rTsNtCCTZ/s727uRaVkQ==
@ -4148,21 +4109,6 @@
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/react-tabs@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.0.tgz#135c67f1f2bd9ada69a3f6e38dd897d459af5fe5"
integrity sha512-oKUwEDsySVC0uuSEH7SHCVt1+ijmiDFAI9p+fHCtuZdqrRDKIFs09zp5nrmu4ggP6xqSx9lj1VSblnDH+n3IBA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-direction" "1.0.0"
"@radix-ui/react-id" "1.0.0"
"@radix-ui/react-presence" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-roving-focus" "1.0.0"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-toggle-group@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-0.1.5.tgz#9e4d65e22c4fc0ba3a42fbc8d5496c430e5e9852"
@ -5746,7 +5692,7 @@
dependencies:
prop-types "^15.7.2"
"@stripe/stripe-js@^1.17.1", "@stripe/stripe-js@^1.35.0":
"@stripe/stripe-js@^1.34.0", "@stripe/stripe-js@^1.35.0":
version "1.35.0"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.35.0.tgz#f809e2e5e0a00f01aa12e8aed0b89d27728c05c0"
integrity sha512-UIuzpbJqgXCTvJhY/aZYvBtaKdMfQgnIv6kkLlfRJ9smZcC4zoPvq3j7k9wobYI+idHAWP4BRiPnqA8lvzJCtg==
@ -6373,16 +6319,11 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@16.9.1", "@types/node@>=12.0.0", "@types/node@>=8.1.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0":
"@types/node@*", "@types/node@16.9.1", "@types/node@>=12.0.0", "@types/node@>=8.1.0", "@types/node@^12.12.6", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0":
version "16.9.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708"
integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
"@types/node@^12.12.6":
version "12.20.55"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
"@types/nodemailer@^6.4.5":
version "6.4.5"
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.5.tgz#09011ac73259245475d1688e4ba101860567dc39"
@ -6511,7 +6452,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@>=16", "@types/react@^18.0.17":
"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@>=16", "@types/react@^18.0.17", "@types/react@^18.0.9":
version "18.0.17"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.17.tgz#4583d9c322d67efe4b39a935d223edcc7050ccf4"
integrity sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==
@ -6520,24 +6461,6 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@18.0.9":
version "18.0.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878"
integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^18.0.9":
version "18.0.18"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.18.tgz#9f16f33d57bc5d9dca848d12c3572110ff9429ac"
integrity sha512-6hI08umYs6NaiHFEEGioXnxJ+oEhY3eRz8VCUaudZmGdtvPviCJB8mgaMxaDWAdPSYd4eFavrPk2QIolwbLYrg==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/responselike@*", "@types/responselike@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
@ -7846,7 +7769,7 @@ autolinker@^3.11.0:
dependencies:
tslib "^2.3.0"
autoprefixer@^10.3.4, autoprefixer@^10.4.7, autoprefixer@^10.4.8:
autoprefixer@^10.4.7, autoprefixer@^10.4.8:
version "10.4.8"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.8.tgz#92c7a0199e1cfb2ad5d9427bd585a3d75895b9e5"
integrity sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==
@ -10220,11 +10143,6 @@ denque@^2.0.1:
resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a"
integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==
depd@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
integrity sha512-Jlk9xvkTDGXwZiIDyoM7+3AsuvJVoyOpRupvEVy9nX3YO3/ieZxhlgh8GpLNZ8AY7HjO6y2YwpMSh1ejhu3uIw==
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@ -11301,7 +11219,7 @@ eslint@8.4.1:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
eslint@^8.15.0, eslint@^8.20.0:
eslint@^8.20.0:
version "8.23.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040"
integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==
@ -13072,11 +12990,6 @@ gray-matter@^4.0.3:
section-matter "^1.0.0"
strip-bom-string "^1.0.0"
gsap@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/gsap/-/gsap-3.11.0.tgz#5a45da0f35b49e2c479e1e274feb6ac51e86478b"
integrity sha512-TV5aFGqXht+0o/CelnhCikSe3QGeG+q1XA/fyFFsMzesILHgWgFWIz0NuXIgcMaL5h7MG2l+j0BTupS5YyYkrw==
gtoken@^5.0.4:
version "5.3.2"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.2.tgz#deb7dc876abe002178e0515e383382ea9446d58f"
@ -13546,16 +13459,6 @@ http-cache-semantics@^4.0.0:
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-errors@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
integrity sha512-STnYGcKMXL9CGdtpeTFnLmgMSHTTNQJSHxiC4DETHKf934Q160Ht5pljrNeH24S0O9xUN+9vsDJZdZtk5js6Ww==
dependencies:
depd "1.1.1"
inherits "2.0.3"
setprototypeof "1.0.3"
statuses ">= 1.3.1 < 2"
http-errors@1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
@ -13684,11 +13587,6 @@ ical.js@^1.4.0:
resolved "https://registry.yarnpkg.com/ical.js/-/ical.js-1.5.0.tgz#23213accd1d8f7248d01705acb06270a70d20662"
integrity sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==
iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -14439,7 +14337,7 @@ is-shared-array-buffer@^1.0.1, is-shared-array-buffer@^1.0.2:
dependencies:
call-bind "^1.0.2"
is-stream@1.1.0, is-stream@^1.0.0, is-stream@^1.1.0:
is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@ -16554,17 +16452,7 @@ methods@^1.1.2, methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micro@9.3.4:
version "9.3.4"
resolved "https://registry.yarnpkg.com/micro/-/micro-9.3.4.tgz#745a494e53c8916f64fb6a729f8cbf2a506b35ad"
integrity sha512-smz9naZwTG7qaFnEZ2vn248YZq9XR+XoOH3auieZbkhDL4xLOxiE+KqG8qqnBeKfXA9c1uEFGCxPN1D+nT6N7w==
dependencies:
arg "4.1.0"
content-type "1.0.4"
is-stream "1.1.0"
raw-body "2.3.2"
micro@^9.4.1:
micro@^9.4.0, micro@^9.4.1:
version "9.4.1"
resolved "https://registry.yarnpkg.com/micro/-/micro-9.4.1.tgz#3a7eedd96718d8569a324475cd1967441df4b3c7"
integrity sha512-Lpjcbp6Y9GJIfewxDfTmu9eW0rt0MGo+Gs1d3yJLFa7mhErtKkCngGhDbA/O1gqUjEwsHh+jWPg8BJ0Bx4AgFA==
@ -17524,7 +17412,7 @@ next-validations@^0.2.0:
resolved "https://registry.yarnpkg.com/next-validations/-/next-validations-0.2.1.tgz#68010c9b017ba48eec4f404fd42eb9b0c7324737"
integrity sha512-92pR14MPTTx0ynlvYH2TwMf7WiGiznNL/l0dtZyKPw3x48rcMhwEZrP1ZmsMJwzp5D+U+sY2deexeLWC8rlNtQ==
next@^12.2.0, next@^12.2.5:
next@^12.2.5:
version "12.2.5"
resolved "https://registry.yarnpkg.com/next/-/next-12.2.5.tgz#14fb5975e8841fad09553b8ef41fe1393602b717"
integrity sha512-tBdjqX5XC/oFs/6gxrZhjmiq90YWizUYU6qOWAfat7zJwrwapJ+BYgX2PmiacunXMaRpeVT4vz5MSPSLgNkrpA==
@ -19478,16 +19366,6 @@ range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
integrity sha512-Ss0DsBxqLxCmQkfG5yazYhtbVVTJqS9jTsZG2lhrNwqzOk2SUC7O/NB/M//CkEBqsrtmlNgJCPccJGuYSFr6Vg==
dependencies:
bytes "3.0.0"
http-errors "1.6.2"
iconv-lite "0.4.19"
unpipe "1.0.0"
raw-body@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
@ -19687,11 +19565,6 @@ react-element-to-jsx-string@^14.3.4:
is-plain-object "5.0.0"
react-is "17.0.2"
react-fast-marquee@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/react-fast-marquee/-/react-fast-marquee-1.3.5.tgz#e53995027102fbec92da90606d7ca89703db9903"
integrity sha512-eOqLoz4iVVBvi2wN/web8hd2XX9y2Z6CYR7g++7nTVHlTOXBtqyARQJ9rYNpbp179hAzloMx0yBFAo8LpNYmKQ==
react-feather@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.10.tgz#0e9abf05a66754f7b7bb71757ac4da7fb6be3b68"
@ -19713,12 +19586,12 @@ react-gtm-module@^2.0.11:
resolved "https://registry.yarnpkg.com/react-gtm-module/-/react-gtm-module-2.0.11.tgz#14484dac8257acd93614e347c32da9c5ac524206"
integrity sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==
react-hook-form@^7.31.1, react-hook-form@^7.34.2:
react-hook-form@^7.33.1, react-hook-form@^7.34.2:
version "7.34.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.34.2.tgz#9ac6d1a309a7c4aaa369d1269357a70e9e9bf4de"
integrity sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==
react-hot-toast@^2.1.1, react-hot-toast@^2.3.0:
react-hot-toast@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.3.0.tgz#70b3d183ac2a4afb6b17cda4a7f4cfe02e730415"
integrity sha512-/RxV+bfjld7tSJR1SCLzMAXgFuNW7fCpK6+vbYqfmbGSWcqTMz2rizrvfWKvtcPH5HK0NqxmBaC5SrAy1F42zA==
@ -19800,11 +19673,6 @@ react-live-chat-loader@^2.7.3:
resolved "https://registry.yarnpkg.com/react-live-chat-loader/-/react-live-chat-loader-2.7.3.tgz#a66a7d64eacdf0a680570b4e9a99639a88bffaae"
integrity sha512-VviVqnF3PYDBJ77JiPZv4cpulx806L22WfsIQxvlxlEWmzKNp/0lEs57uP6EJcE+d1jQwGcR9DIsj5qouE6OkA==
react-masonry-css@^1.0.16:
version "1.0.16"
resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"
integrity sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==
react-merge-refs@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
@ -21079,11 +20947,6 @@ setimmediate@^1.0.4, setimmediate@^1.0.5:
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
setprototypeof@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
integrity sha512-9jphSf3UbIgpOX/RKvX02iw/rN2TKdusnsPpGfO/rkcsrd+IRqgHZb4VGnmL0Cynps8Nj2hN45wsi30BzrHDIw==
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
@ -21523,7 +21386,7 @@ statuses@2.0.1, statuses@^2.0.0:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
"statuses@>= 1.3.1 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@ -21870,7 +21733,7 @@ stripe@*:
"@types/node" ">=8.1.0"
qs "^6.10.3"
stripe@^9.1.0, stripe@^9.16.0:
stripe@^9.16.0:
version "9.16.0"
resolved "https://registry.yarnpkg.com/stripe/-/stripe-9.16.0.tgz#94c24549c91fced457b9e3259e8a1a1bdb6dbd0e"
integrity sha512-Dn8K+jSoQcXjxCobRI4HXUdHjOXsiF/KszK49fJnkbeCFjZ3EZxLG2JiM/CX+Hcq27NBDtv/Sxhvy+HhTmvyaQ==
@ -22744,71 +22607,155 @@ turbo-android-arm64@1.4.3:
resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.4.3.tgz#d531c6935134d8cae9f31f61db47d13bd227bb93"
integrity sha512-ZUvdoEHJkTkOFOO9PKWYrdONDBVqkNsvwEMufTVf07RXgqmbXDPkznzT4hcQm6xXyqWqJdjgSAMdlm+2nNE1Og==
turbo-darwin-64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.2.9.tgz#001159794757f77c4b016fc56d045c5e7bfc9510"
integrity sha512-rVwDQpi6p0GwTiqSsvtA1b3RvKl8l2y+ElZ3EKGiIIJYZt1D6wBMJoADaZ9uZ/LWkT+WKfAWNtKdwRmuBAOS6g==
turbo-darwin-64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.4.3.tgz#123e214d9070ec6ac81468dc7a1cb02a459fafd7"
integrity sha512-gapoVm5qbu2TJS4lJ6fM3o2eAkLyXSxHihw/4NRAYmwHCH3at1/cIAnRcctB/HLL3ZaB/p3HKb8mnI7k6xNHOw==
turbo-darwin-arm64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.2.9.tgz#fb19615984d1780fdcdd9e54a607efb46a700e72"
integrity sha512-j7NgQHkQWWODw1I/saiqmjjD54uGAEq0qTTtLI3RoLaA+yI+awXmHwsiHRqsvGSyGJlBoKBcbxXkekLf21q3GA==
turbo-darwin-arm64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.4.3.tgz#9fa062c3ffa9208d0e2fa0155dcb48a290e7c109"
integrity sha512-XUe6FTsHamEH7FfNslYYO04yecAaguhZuwW4kE9B/BAP8MUYsmVqONauLPyE/YqM6pf2K0xwVe+RlEGf53CWbg==
turbo-freebsd-64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.2.9.tgz#edb2ee840ba80c257068e339d10ac460c1f9fb10"
integrity sha512-+tLb3iCOrIeGrcOJZYey5mD9qgNgKYuwRRg6FeX/6TDITvZXcCS50A2uRbaD/PQzQKs1lHcshiCe/DRtbvJ63g==
turbo-freebsd-64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.4.3.tgz#ffb4a939ca0000ec91114d2bbddc491c4835e862"
integrity sha512-1CAjXmDClgMXdWZXreUfAbGBB2WB9TZHfJIdsgnDqt4fIcFGChknzYqc+Fj3tGHAczMpinGjBbWIzFuxOq/ofQ==
turbo-freebsd-arm64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.2.9.tgz#aebfb5b07a2dd6e9211fb6d9827c2f2288e2ca73"
integrity sha512-gwI8jocTf036kc9GI1BebzftxrkT5pewHPA2iqvAXAJpX01G1x1iGcl8/uIbkbL5hp038nu+l2Kb+lRI96sJuA==
turbo-freebsd-arm64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.4.3.tgz#0bab3e2ccfac1ecc9ed9a30b0ab80f379fe3a788"
integrity sha512-j5C7j/vwabPKpr5d6YlLgHGHBZCOcXj3HdkBshDHTQ0wghH0NuCUUaesYxI3wva/4/Ec0dhIrb20Laa/HMxXLA==
turbo-linux-32@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.2.9.tgz#1462e45776d3ce93c57f2014745e7edb60910a44"
integrity sha512-Rm47bIsCHIae/DkXJ58YrWvdh8o4Ug9U4VnTDb9byXrz2B7624ol9XdfpXv429z7LXkQR1+WnwCMwFB4K6DyuQ==
turbo-linux-32@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.4.3.tgz#96deb5ebedfe6b97fd8ecc0fa40ff1c891c4c04f"
integrity sha512-vnc+StXIoQEnxIU43j7rEz/J+v+RV4dbUdUolBq0k9gkUV8KMCcqPkIa753K47E2KLNGKXMaYDI6AHQX1GAQZg==
turbo-linux-64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.2.9.tgz#0eef4a6f6f267b773ea58c4bda86922092eda3eb"
integrity sha512-8Gqi+TzEdmOmxxAukU0NO0JlIqdm98C97u9qEsxWrXTFL/xL21gKCixqsBTEO7JOISC4M8VjArxjSsITRbkD5g==
turbo-linux-64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.4.3.tgz#ed0152e724c78dea090b2d60d29258110bdfb14a"
integrity sha512-KAUeIa8Ejt6BLrBGbVurlrjDxqh62tu75D4cqKqKfzWspcbEtmdqlV6qthXfm8SlzGSNuQXX0+qXEWds2FIZXg==
turbo-linux-arm64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.2.9.tgz#57777d356bf74ef2cc284998ef86fe19c77b92c2"
integrity sha512-FVIeM7koUtyu1cNAJhPYjb90kL/ICdWoJr4PoZZYnqty5sxLsBg75bVErEDQeDzKQvwXLlcax2lEzHvaSyn/wg==
turbo-linux-arm64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.4.3.tgz#74fac47575e6b5af5830d26a56493859d09e4c7c"
integrity sha512-rzB7w+RHCQkKr8aDxxozv/IzdN976CYyBiRocSf9QGU73uyAg8pCo3i0MiENSRjDC+tUbdbu2lEUwGXf9ziB9Q==
turbo-linux-arm@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.2.9.tgz#2424016fb926143682f9029bd86a8d455979e075"
integrity sha512-OS+XCWiGFbuM7UNBVQdVbIJqxhVu9Sr2WxQgDcGZpCYn32yLLPlWDDGL0Cl/EG006J9k+VS1e4OzyM6kfMxS9Q==
turbo-linux-arm@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.4.3.tgz#ac42b3c5918fe06270cb8dfbe31442a48cc38fc3"
integrity sha512-zZNoHUK5ioFyxAngh8tHe763Dzb22ne3LJkaZn0ExkFHJtWClWv536lPcDuQPpIH9W9iz5OwPKtN32DNpNwk8A==
turbo-linux-mips64le@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.2.9.tgz#e3bb35f53cb84ff836cf42fc576b6197c92a5e8a"
integrity sha512-2zVBnOVivWGpl51qO/lycfw7euM4b04AXYUmhsWkUN3FygIwyNgjuiMU8rxQOlu9VGX8X+WXkX2gfbgTovTeFw==
turbo-linux-mips64le@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.4.3.tgz#d796261795e4fc29935cbffdadfbc11d83cbe616"
integrity sha512-Ztr1BM5NiUsHWjB7zpkP2RpRDA/fjbLaCbkyfyGlLmVkrSkh05NFBD03IWs2LSLy/wb6vRpL3MQ4FKcb97Tn8w==
turbo-linux-ppc64le@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.2.9.tgz#130041711579a1b6b3fe774d85c394425d45c0ac"
integrity sha512-EGgKyzf8IhodOF32BvE3Zlgbr/dSGuUbemC9RGSuhF1F1PMnP1nYS/t3JWN5QKZU4O2uWiIyLdC/0ZjtcGAcZQ==
turbo-linux-ppc64le@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.4.3.tgz#b7d043efa27290e074b7aba45bb298fc86bd8cfe"
integrity sha512-tJaFJWxwfy/iLd69VHZj6JcXy9hO8LQ+ZUOna/p/wiy5WrFVgEYlD+4gfECfRZ+52EIelMgXl97vACaN1WMhLw==
turbo-windows-32@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.2.9.tgz#89a0d8463dffba01627d69998bfe6ea5fa975511"
integrity sha512-XrMJMUtewlfksBUB0R7Tyw16IoqshVl6f/3R2ccMccddEMcvak0oW03FK9n+Y4F+wyIoJ22AVhu8jMv+HgEehA==
turbo-windows-32@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.4.3.tgz#3dac4678f1d74ccf0ac187e738d30a557932fbad"
integrity sha512-w9LyYd+DW3PYFXu9vQiie5lfdqmVIKLV0h181C49hempkIXfgQAosXfaugYWDwBc0GEBoBIQB0vGQKE7gt5nzA==
turbo-windows-64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.2.9.tgz#a55e3f32996ce4a8a538f9667748e7a1129ed10e"
integrity sha512-ewhj4MrqcMpW/keag4xG7YRLTJ7PzcqBc6Kc96OGD2qfK/uJV/r7H3Xt09WuYHRWwPgGEeNn8utpqdqbYfCVDw==
turbo-windows-64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.4.3.tgz#b493ab62db70c4c147cf0dc56dc812ea2e15cf3c"
integrity sha512-qPCqemxxOrXyqqig3fVQozRkOwo5oJSsQ3FTZE5YlNu2NwwWvY1mC0X4WTZIDsbj4oHqr0riqC7RGKbjQm1IIQ==
turbo-windows-arm64@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.2.9.tgz#3dc635c0031a0998be0322f9a258553f7cb4eb6e"
integrity sha512-B8BoNb/yZWAyKwQUbs2+UFzLmOu/WGv/+ADT6SQfI8jOaTenS7Od4bbMsGJT0iXcqv+v8TcWKX83KmQ6gxBQpg==
turbo-windows-arm64@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.4.3.tgz#be4b38994cb3f1ceb36940ff6d6b227fa2ba53bf"
integrity sha512-djnOOBjw33AnUx2SR6TMOpDr3nKLnVD+HcZvnQz70HyE331AKWjBoEE4rtUOteLAfViWAp3afbiljFSOnbU00Q==
turbo@1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.2.9.tgz#61257149a2a29966c9941a16e0b5ad88b07b4e79"
integrity sha512-aPGzZqmUHE9yx9TS7wcAJnDmXiuQSNXDwU5b1KrgNlFuID18TL443wna79p7k4awmf4Yuhu1cSZIvO+se72iVQ==
optionalDependencies:
turbo-darwin-64 "1.2.9"
turbo-darwin-arm64 "1.2.9"
turbo-freebsd-64 "1.2.9"
turbo-freebsd-arm64 "1.2.9"
turbo-linux-32 "1.2.9"
turbo-linux-64 "1.2.9"
turbo-linux-arm "1.2.9"
turbo-linux-arm64 "1.2.9"
turbo-linux-mips64le "1.2.9"
turbo-linux-ppc64le "1.2.9"
turbo-windows-32 "1.2.9"
turbo-windows-64 "1.2.9"
turbo-windows-arm64 "1.2.9"
turbo@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.4.3.tgz#7221972f47a28bfcb53609e97db9810d2c3a265b"
@ -22987,11 +22934,6 @@ typeorm@0.2.41:
yargs "^17.0.1"
zen-observable-ts "^1.0.0"
typescript@^4.6.4:
version "4.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
@ -24667,16 +24609,16 @@ zod-prisma@^0.5.4:
parenthesis "^3.1.8"
ts-morph "^13.0.2"
zod@^3.16.0, zod@^3.18.0:
version "3.18.0"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc"
integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==
zod@^3.17.3:
version "3.17.3"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.3.tgz#86abbc670ff0063a4588d85a4dcc917d6e4af2ba"
integrity sha512-4oKP5zvG6GGbMlqBkI5FESOAweldEhSOZ6LI6cG+JzUT7ofj1ZOC0PJudpQOpT1iqOFpYYtX5Pw0+o403y4bcg==
zod@^3.18.0:
version "3.18.0"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc"
integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==
zwitch@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"