Upgrades yarn v1 to v3. Improves CI times by 2x (#7738)

* Create env file workflow

* Add env-cache

* Fix env setter

* Fix

* Another fix

* Fix

* Fix

* Fixes

* FFS

* Fix

* Fix

* Fix

* Fix

* Fix

* Cache fixes

* Fixes

* Adds skipping steps

* db-cache fixes

* Test

* Cache fixes

* e2e

* Possible caching conflicts

* Running out of ideas

* Caching is hard

* One more time

* cache-build not skipping

* Fingers crossed

* a

* Test

* Pls

* Please

* LFG

* Build fix

* fix

* Whitespace!!

* Zomars/cal 884 paid events not sending the link (#7318)

* WIP

* Sends correct emails for paid bookings

* Update PaymentService.ts

* Update webhook.ts

* Update webhook.ts

* Update settings back button redirect link (#7403)

* fix(schedule): close on click #7143

* fix(EventSetupTab): validLocations length will always match validLocations length #7138

* fix(SettingsLayout): go back to right route #7379

* feat: get country code from ip geolocation (#6880)

* feat: get coutnry code from ip geolocation

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* fix: create new api route for fetching code

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* chore: replace city with country

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* refactor: create hook for country

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* Team Workflows (#7038)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Add destination calendar name to DestinationCalendarSelector (#6701)

* Add destination calendar name

* Type fix

* Search through calendars only for destination calendar credential

* Refactor get connected calendars

* Clean up

---------

Co-authored-by: zomars <zomars@me.com>

* Update viewer.tsx (#7428)

* Fix - add team members to emails (#7207)

* On booking add team members & translation

* Add team members to round robin create

* Only update calendars on reschedule if there is a calendar reference

* Send email on reschedules

* Send team email on cancelled event

* Add team members to calendar event description

* Clean up

* Convert other emails to organizer & teams

* Type check fixes

* More type fixes

* Change organizer scheduled input to an object

* early return updateCalendarEvent

* Introduce team member type

* Fix type errors

* Put team members before attendees

* Remove lodash cloneDeep

* Update packages/core/EventManager.ts

Co-authored-by: Omar López <zomars@me.com>

* Remove booking select object

* Revert "Remove booking select object"

This reverts commit 9f121ff4eb.

* Refactor email manager (#7270)

Co-authored-by: zomars <zomars@me.com>

* Type change

* Remove conditional check for updateAllCalendarEvents

---------

Co-authored-by: zomars <zomars@me.com>

* Typefix

* Updates webhook response

* Update pr.yml

* Update action.yml

* Update action.yml

* Update action.yml

* Update action.yml

* Update action.yml

* Is this redundant?

* Removed setup

* Update action.yml

* Update action.yml

* Consolitades setup step

* Revert "Consolitades setup step"

This reverts commit 5e8d1983cc.

* Fix?

* One more time

* Revert "One more time"

This reverts commit fd8b559a13.

* Benchmarking buildjet

* Update action.yml

* Re-introduce setup

* Adds embeds to missing pro cache

* Lint fixes

* Adds prettier ignore

* Upgrades to yarn 3

* Updates lockfile

* Reverts CI to ubuntu

* Testing new yarn install

* We cannot use immutable due to our private submodules

* Adds CI skip

* Fixes

* Adds plugin

* Forces local embed package

* Moves eslint to root

* Update yarn.lock

* Playwright fixes

* Embed test fixes

* Splits embed react tests

* Splits embed react tests

* Removes install step to benchmark

* Update playwright.config.ts

* One playwright config for all

* More test fixes

* Update basic.e2e.ts

* Added typescript as a global monorepo dev

* Update to v18

* Update yarn.lock

* Update env-create-file.yml

* Update .github/workflows/pr.yml

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Esaú Morais <55207584+esau-morais@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
This commit is contained in:
Omar López 2023-03-15 15:01:04 -07:00 committed by GitHub
parent f20d78bec1
commit 54cefcb16e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 41893 additions and 29127 deletions

6
.eslintignore Normal file
View File

@ -0,0 +1,6 @@
node_modules
**/**/node_modules
**/**/.next
**/**/public
packages/prisma/zod
apps/web/public/embed

View File

@ -20,6 +20,7 @@ runs:
with:
path: |
${{ github.workspace }}/apps/web/.next
${{ github.workspace }}/apps/web/public/embed
**/.turbo/**
**/dist/**
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }}

View File

@ -1,9 +1,21 @@
name: Yarn install
description: "Install all NPM dependencies, caches them and restores if necessary"
########################################################################################
# "yarn install" composite action for yarn 2/3/4+ and "nodeLinker: node-modules" #
#--------------------------------------------------------------------------------------#
# Cache: #
# - Downloaded zip archive (multi-arch, preserved across yarn.lock changes) #
# - Yarn install state (discarded on yarn.lock changes) #
# References: #
# - bench: https://gist.github.com/belgattitude/0ecd26155b47e7be1be6163ecfbb0f0b #
# - vs @setup/node: https://github.com/actions/setup-node/issues/325 #
########################################################################################
name: "Yarn install"
description: "Run yarn install with node_modules linker and cache enabled"
inputs:
node_version:
required: false
default: v18.x
runs:
using: "composite"
steps:
@ -11,9 +23,40 @@ runs:
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node_version }}
cache: "yarn"
- name: Yarn install
- name: Expose yarn config as "$GITHUB_OUTPUT"
id: yarn-config
shell: bash
run: |
yarn install --prefer-offline --frozen-lockfile
echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
# Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
# Yarn cache is also reusable between arch and os.
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-download-cache
with:
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
restore-keys: |
yarn-download-cache-
# Invalidated on yarn.lock changes
- name: Restore yarn install state
id: yarn-install-state-cache
uses: actions/cache@v3
with:
path: .yarn/ci-cache/
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
- name: Install dependencies
shell: bash
run: |
yarn install --inline-builds
yarn prisma generate
env:
# CI optimizations. Overrides yarnrc.yml options (or their defaults) in the CI action.
YARN_ENABLE_IMMUTABLE_INSTALLS: "false" # So it doesn't try to remove our private submodule deps
YARN_ENABLE_GLOBAL_CACHE: "false" # Use local cache folder to keep downloaded archives
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz # Very small speedup when lock does not change
# Other environment variables
HUSKY: "0" # By default do not run HUSKY install

View File

@ -18,5 +18,3 @@ jobs:
days-before-stale: 60
include-only-assigned: true
days-before-close: -1

36
.github/workflows/e2e-embed-react.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: E2E Embed tests and booking flow(for non-embed as well)
on:
workflow_call:
jobs:
e2e-embed:
timeout-minutes: 20
runs-on: ubuntu-latest
services:
postgres:
image: postgres:12.1
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/yarn-playwright-install
- uses: ./.github/actions/env-read-file
- uses: ./.github/actions/cache-db
- uses: ./.github/actions/cache-build
- name: Run Tests
run: yarn test-e2e:embed-react
env:
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results
path: test-results

View File

@ -25,19 +25,12 @@ jobs:
- uses: ./.github/actions/cache-db
- uses: ./.github/actions/cache-build
- name: Run Tests
run: yarn turbo run embed-tests-update-snapshots:ci --scope=@calcom/embed-react --concurrency=1
run: yarn test-e2e:embed
env:
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
- name: Upload embed-core results
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-embed-core
path: packages/embeds/embed-core/playwright/results
- name: Upload embed-react results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-embed-react
path: packages/embeds/embed-react/playwright/results
name: test-results
path: test-results

View File

@ -20,7 +20,7 @@ env:
INPUT_ENV_PAYMENT_FEE_FIXED: 10
INPUT_ENV_SAML_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
INPUT_ENV_SAML_ADMINS: pro@example.com
INPUT_ENV_NEXTAUTH_URL: http://localhost:3000/api/auth
INPUT_ENV_NEXTAUTH_URL: http://127.0.0.1:3000/api/auth
INPUT_ENV_NEXT_PUBLIC_IS_E2E: 1
# INPUT_ENV_EMAIL_FROM: e2e@cal.com
# INPUT_ENV_EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}

View File

@ -21,6 +21,7 @@ jobs:
outputs:
app-store: ${{ steps.filter.outputs.app-store }}
embed: ${{ steps.filter.outputs.embed }}
embed-react: ${{ steps.filter.outputs.embed-react }}
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
@ -34,6 +35,9 @@ jobs:
embed:
- 'apps/web/**'
- 'packages/embeds/**'
embed-react:
- 'apps/web/**'
- 'packages/embeds/**'
env:
name: Create env file
@ -81,6 +85,13 @@ jobs:
uses: ./.github/workflows/e2e-embed.yml
secrets: inherit
e2e-embed-react:
name: E2E React embeds tests
if: ${{ needs.changes.outputs.embed-react == 'true' }}
needs: [changes, lint, build]
uses: ./.github/workflows/e2e-embed-react.yml
secrets: inherit
analyze:
needs: build
uses: ./.github/workflows/nextjs-bundle-analysis.yml

9
.gitignore vendored
View File

@ -87,3 +87,12 @@ apps/api
apps/website
apps/console
apps/auth
# Yarn Modern
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

View File

@ -14,3 +14,4 @@ public
.DS_Store
.eslintignore
packages/prisma/zod
apps/web/public/embed

File diff suppressed because one or more lines are too long

873
.yarn/releases/yarn-3.4.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

7
.yarnrc.yml Normal file
View File

@ -0,0 +1,7 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.4.1.cjs

View File

@ -24,7 +24,6 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@calcom/config": "*",
"eslint": "^8.34.0"
"@calcom/config": "*"
}
}

View File

@ -1,2 +1,2 @@
node_modules
prisma/zod
public/embed

1
apps/web/.prettierignore Normal file
View File

@ -0,0 +1 @@
public/embed

View File

@ -28,9 +28,9 @@
"@calcom/app-store-cli": "*",
"@calcom/core": "*",
"@calcom/dayjs": "*",
"@calcom/embed-core": "*",
"@calcom/embed-react": "*",
"@calcom/embed-snippet": "*",
"@calcom/embed-core": "workspace:*",
"@calcom/embed-react": "workspace:*",
"@calcom/embed-snippet": "workspace:*",
"@calcom/features": "*",
"@calcom/lib": "*",
"@calcom/prisma": "*",
@ -134,7 +134,7 @@
"@calcom/config": "*",
"@calcom/types": "*",
"@microsoft/microsoft-graph-types-beta": "0.15.0-preview",
"@playwright/test": "^1.25.0",
"@playwright/test": "^1.31.2",
"@testing-library/react": "^13.3.0",
"@types/accept-language-parser": "1.5.2",
"@types/async": "^3.2.15",
@ -162,7 +162,6 @@
"copy-webpack-plugin": "^11.0.0",
"detect-port": "^1.3.0",
"env-cmd": "^10.1.0",
"eslint": "^8.34.0",
"mockdate": "^3.0.5",
"module-alias": "^2.2.2",
"msw": "^0.42.3",

View File

@ -2,6 +2,7 @@ import { test as base } from "@playwright/test";
import prisma from "@calcom/prisma";
import type { ExpectedUrlDetails } from "../../../../playwright.config";
import { createBookingsFixture } from "../fixtures/bookings";
import { createEmbedsFixture, createGetActionFiredDetails } from "../fixtures/embeds";
import { createPaymentsFixture } from "../fixtures/payments";
@ -18,6 +19,21 @@ export interface Fixtures {
prisma: typeof prisma;
}
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace PlaywrightTest {
//FIXME: how to restrict it to Frame only
interface Matchers<R> {
toBeEmbedCalLink(
calNamespace: string,
// eslint-disable-next-line
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>,
expectedUrlDetails?: ExpectedUrlDetails
): Promise<R>;
}
}
}
/**
* @see https://playwright.dev/docs/test-fixtures
*/

View File

@ -61,8 +61,12 @@
"tdd": "jest --watch",
"e2e": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --project=@calcom/web",
"e2e:app-store": "QUICK=true yarn playwright test --project=@calcom/app-store",
"e2e:embed": "QUICK=true yarn playwright test --project=@calcom/embed-core",
"e2e:embed-react": "QUICK=true yarn playwright test --project=@calcom/embed-react",
"test-e2e": "yarn db-seed && yarn e2e",
"test-e2e:app-store": "yarn db-seed && yarn e2e:app-store",
"test-e2e:embed": "yarn db-seed && yarn e2e:embed",
"test-e2e:embed-react": "yarn db-seed && yarn e2e:embed-react",
"test-playwright": "yarn playwright test --config=playwright.config.ts",
"test": "jest",
"type-check": "turbo run type-check",
@ -70,6 +74,7 @@
},
"devDependencies": {
"@deploysentinel/playwright": "^0.3.3",
"@playwright/test": "^1.31.2",
"@snaplet/copycat": "^0.3.0",
"@types/dompurify": "^2.4.0",
"@types/jest": "^28.1.7",
@ -82,10 +87,12 @@
"jest-watch-typeahead": "^2.0.0",
"lint-staged": "^12.5.0",
"prettier": "^2.8.4",
"ts-jest": "^28.0.8"
"ts-jest": "^28.0.8",
"typescript": "^4.9.4"
},
"dependencies": {
"city-timezones": "^1.2.1",
"eslint": "^8.34.0",
"turbo": "^1.4.3"
},
"resolutions": {
@ -115,7 +122,7 @@
"schema": "packages/prisma/schema.prisma",
"seed": "ts-node --transpile-only ./packages/prisma/seed.ts"
},
"packageManager": "yarn@1.22.17",
"packageManager": "yarn@3.4.1",
"syncpack": {
"filter": "^(?!@calcom).*",
"semverRange": ""

View File

@ -5,8 +5,8 @@
"main": "./index.ts",
"description": "Cal Video is the in-house web-based video conferencing platform powered by Daily.co, which is minimalistic and lightweight, but has most of the features you need.",
"dependencies": {
"@calcom/prisma": "*",
"@calcom/lib": "*"
"@calcom/lib": "*",
"@calcom/prisma": "*"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -9,8 +9,8 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/ui": "*",
"react-hook-form": "^7.43.3",
"ews-javascript-api": "^0.11.0"
"ews-javascript-api": "^0.11.0",
"react-hook-form": "^7.43.3"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -9,8 +9,8 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/ui": "*",
"react-hook-form": "^7.43.3",
"ews-javascript-api": "^0.11.0"
"ews-javascript-api": "^0.11.0",
"react-hook-form": "^7.43.3"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -5,8 +5,8 @@
"main": "./index.ts",
"description": "Microsoft Teams is a business communication platform and collaborative workspace included in Microsoft 365. It offers workspace chat and video conferencing, file storage, and application integration. Both web versions and desktop/mobile applications are available. NOTE: MUST HAVE A WORK / SCHOOL ACCOUNT",
"dependencies": {
"@calcom/prisma": "*",
"@calcom/lib": "*"
"@calcom/lib": "*",
"@calcom/prisma": "*"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -49,7 +49,7 @@ test.describe("Routing Forms", () => {
await page.reload();
expect(await page.inputValue(`[data-testid="description"]`)).toMatch(description);
expect(await page.inputValue(`[data-testid="description"]`)).toBe(description);
expect(await page.locator('[data-testid="field"]').count()).toBe(1);
await expectCurrentFormToHaveFields(page, { 0: field }, types);

View File

@ -5,8 +5,8 @@
"main": "./index.ts",
"description": "Tandem is a new virtual office space that allows teams to effortlessly connect as though they are in a physical office, online. Through co-working rooms, available statuses, live real-time video call, and chat options, you can see whos around, talk and collaborate in one click. It works cross-platform with both desktop and mobile versions.",
"dependencies": {
"@calcom/prisma": "*",
"@calcom/lib": "*"
"@calcom/lib": "*",
"@calcom/prisma": "*"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -5,8 +5,8 @@
"main": "./index.ts",
"description": "Zoom is a secure and reliable video platform that supports all of your online communication needs. It can provide everything from one on one meetings, chat, phone, webinars, and large-scale online events. Available with both desktop, web, and mobile versions.",
"dependencies": {
"@calcom/prisma": "*",
"@calcom/lib": "*"
"@calcom/lib": "*",
"@calcom/prisma": "*"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -3,7 +3,6 @@
"sideEffects": false,
"version": "0.0.0",
"private": true,
"scripts": {},
"dependencies": {
"@calcom/dayjs": "*",
"@calcom/lib": "*",

View File

@ -43,8 +43,8 @@
}
},
"devDependencies": {
"@playwright/test": "^1.31.2",
"autoprefixer": "^10.4.12",
"eslint": "^8.34.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.18",
"tailwindcss": "^3.2.1",

View File

@ -1,3 +0,0 @@
async function globalSetup(/* config: FullConfig */) {}
export default globalSetup;

View File

@ -1,204 +0,0 @@
import type { PlaywrightTestConfig, Frame } from "@playwright/test";
import { devices, expect } from "@playwright/test";
import * as path from "path";
// eslint-disable-next-line @typescript-eslint/no-var-requires
require("dotenv").config({ path: "../../../../../.env" });
const outputDir = path.join("../results");
const testDir = path.join("../tests");
const quickMode = process.env.QUICK === "true";
const CI = process.env.CI;
const config: PlaywrightTestConfig = {
forbidOnly: !!CI,
retries: quickMode && !CI ? 0 : 1,
workers: 1,
timeout: 60_000,
reporter: [
[CI ? "github" : "list"],
["@deploysentinel/playwright"],
[
"html",
{ outputFolder: path.join(__dirname, "..", "reports", "playwright-html-report"), open: "never" },
],
["junit", { outputFile: path.join(__dirname, "..", "reports", "results.xml") }],
],
globalSetup: require.resolve("./globalSetup"),
outputDir,
expect: {
toMatchSnapshot: {
// Opacity transitions can cause small differences
// Every month the rendered month changes failing the snapshot tests. So, increase the threshold to catch major bugs only.
maxDiffPixelRatio: 0.1,
},
},
webServer: {
// Run servers in parallel as Playwright doesn't support two different webserver commands at the moment See https://github.com/microsoft/playwright/issues/8206
command: "yarn run-p 'embed-dev' 'embed-web-start'",
port: 3100,
timeout: 60_000,
reuseExistingServer: !CI,
},
use: {
baseURL: "http://localhost:3100",
locale: "en-US",
trace: "retain-on-failure",
headless: !!CI || !!process.env.PLAYWRIGHT_HEADLESS,
},
projects: [
{
name: "chromium",
testDir,
use: { ...devices["Desktop Chrome"] },
},
quickMode
? {}
: {
name: "firefox",
testDir,
use: { ...devices["Desktop Firefox"] },
},
quickMode
? {}
: {
name: "webkit",
testDir,
use: { ...devices["Desktop Safari"] },
},
],
};
export type ExpectedUrlDetails = {
searchParams?: Record<string, string | string[]>;
pathname?: string;
origin?: string;
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace PlaywrightTest {
//FIXME: how to restrict it to Frame only
interface Matchers<R> {
toBeEmbedCalLink(
calNamespace: string,
// eslint-disable-next-line
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>,
expectedUrlDetails?: ExpectedUrlDetails
): Promise<R>;
}
}
}
expect.extend({
async toBeEmbedCalLink(
iframe: Frame,
calNamespace: string,
//TODO: Move it to testUtil, so that it doesn't need to be passed
// eslint-disable-next-line
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>,
expectedUrlDetails: ExpectedUrlDetails = {}
) {
if (!iframe || !iframe.url) {
return {
pass: false,
message: () => `Expected to provide an iframe, got ${iframe}`,
};
}
const u = new URL(iframe.url());
const frameElement = await iframe.frameElement();
if (!(await frameElement.isVisible())) {
return {
pass: false,
message: () => `Expected iframe to be visible`,
};
}
const pathname = u.pathname;
const expectedPathname = expectedUrlDetails.pathname + "/embed";
if (expectedPathname && expectedPathname !== pathname) {
return {
pass: false,
message: () => `Expected pathname to be ${expectedPathname} but got ${pathname}`,
};
}
const origin = u.origin;
const expectedOrigin = expectedUrlDetails.origin;
if (expectedOrigin && expectedOrigin !== origin) {
return {
pass: false,
message: () => `Expected origin to be ${expectedOrigin} but got ${origin}`,
};
}
const searchParams = u.searchParams;
const expectedSearchParams = expectedUrlDetails.searchParams || {};
for (const [expectedKey, expectedValue] of Object.entries(expectedSearchParams)) {
const value = searchParams.get(expectedKey);
if (value !== expectedValue) {
return {
message: () => `${expectedKey} should have value ${expectedValue} but got value ${value}`,
pass: false,
};
}
}
let iframeReadyCheckInterval;
const iframeReadyEventDetail = await new Promise(async (resolve) => {
iframeReadyCheckInterval = setInterval(async () => {
const iframeReadyEventDetail = await getActionFiredDetails({
calNamespace,
actionType: "linkReady",
});
if (iframeReadyEventDetail) {
resolve(iframeReadyEventDetail);
}
}, 500);
});
clearInterval(iframeReadyCheckInterval);
//At this point we know that window.initialBodyVisibility would be set as DOM would already have been ready(because linkReady event can only fire after that)
const {
visibility: visibilityBefore,
background: backgroundBefore,
initialValuesSet,
} = await iframe.evaluate(() => {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
visibility: window.initialBodyVisibility,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
background: window.initialBodyBackground,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
initialValuesSet: window.initialValuesSet,
};
});
expect(initialValuesSet).toBe(true);
expect(visibilityBefore).toBe("hidden");
expect(backgroundBefore).toBe("transparent");
const { visibility: visibilityAfter, background: backgroundAfter } = await iframe.evaluate(() => {
return {
visibility: document.body.style.visibility,
background: document.body.style.background,
};
});
expect(visibilityAfter).toBe("visible");
expect(backgroundAfter).toBe("transparent");
if (!iframeReadyEventDetail) {
return {
pass: false,
message: () => `Iframe not ready to communicate with parent`,
};
}
return {
pass: true,
message: () => `passed`,
};
},
});
export default config;

View File

@ -1,85 +0,0 @@
import { test as base, Page } from "@playwright/test";
export interface Fixtures {
addEmbedListeners: (calNamespace: string) => Promise<void>;
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>;
}
export const test = base.extend<Fixtures>({
addEmbedListeners: async ({ page }: { page: Page }, use) => {
await use(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.initialBodyVisibility = document.body.style.visibility;
// 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 }
);
});
},
getActionFiredDetails: async ({ page }, use) => {
await use(async ({ calNamespace, actionType }) => {
if (!page.isClosed()) {
return await page.evaluate(
({ actionType, calNamespace }) => {
//@ts-ignore
return window.eventsFiredStoreForPlaywright[`${actionType}-${calNamespace}`];
},
{ actionType, calNamespace }
);
}
});
},
});

View File

@ -1,8 +1,9 @@
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import type { Fixtures } from "../fixtures/fixtures";
import { test } from "../fixtures/fixtures";
import { test } from "@calcom/web/playwright/lib/fixtures";
import type { Fixtures } from "@calcom/web/playwright/lib/fixtures";
import {
todo,
getEmbedIframe,

View File

@ -41,6 +41,7 @@
}
},
"devDependencies": {
"@playwright/test": "^1.31.2",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@vitejs/plugin-react": "1.3.2",
@ -50,7 +51,7 @@
"vite": "^4.1.2"
},
"dependencies": {
"@calcom/embed-core": "*",
"@calcom/embed-snippet": "*"
"@calcom/embed-core": "workspace:*",
"@calcom/embed-snippet": "workspace:*"
}
}

View File

@ -1,33 +0,0 @@
import { PlaywrightTestConfig, devices } from "@playwright/test";
import path from "path";
//TODO: Move the common config to embed-playwright-config and let core and react use the base. Along with config there would be base fixtures and expect custom matchers as well.
import baseConfig from "@calcom/embed-core/playwright/config/playwright.config";
const testDir = path.join("../tests");
const projects = baseConfig.projects?.map((project) => {
if (!project.name) {
return {};
}
return {
...project,
testDir,
};
});
const config: PlaywrightTestConfig = {
...baseConfig,
webServer: {
command: "yarn run-p 'embed-dev' 'embed-web-start'",
port: 3000,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
},
use: {
...baseConfig.use,
baseURL: "http://localhost:3101",
},
projects,
};
export default config;

View File

@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import { test } from "@calcom/embed-core/playwright/fixtures/fixtures";
import { getEmbedIframe } from "@calcom/embed-core/playwright/lib/testUtils";
import { test } from "@calcom/web/playwright/lib/fixtures";
test("Inline Usage Snapshot", async ({ page, getActionFiredDetails, addEmbedListeners }) => {
//TODO: Do it with page.goto automatically

View File

@ -24,11 +24,10 @@
],
"types": "./dist/index.d.ts",
"devDependencies": {
"eslint": "^8.34.0",
"typescript": "^4.9.4",
"vite": "^4.1.2"
},
"dependencies": {
"@calcom/embed-core": "*"
"@calcom/embed-core": "workspace:*"
}
}

View File

@ -6,7 +6,6 @@
"dependencies": {
"@typescript-eslint/parser": "^5.52.0",
"@typescript-eslint/utils": "^5.52.0",
"eslint": "^8.34.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},

View File

@ -6,16 +6,16 @@
"version": "1.0.0",
"main": "index.ts",
"dependencies": {
"@calcom/dayjs": "*",
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/trpc": "*",
"@calcom/ui": "*",
"bcryptjs": "^2.4.3",
"handlebars": "^4.7.7",
"jose": "^4.13.1",
"next-auth": "^4.20.1",
"nodemailer": "^6.7.8",
"otplib": "^12.0.1",
"@calcom/dayjs": "*",
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/trpc": "*",
"@calcom/ui": "*"
"otplib": "^12.0.1"
}
}

View File

@ -6,12 +6,12 @@
"version": "1.0.0",
"main": "index.ts",
"dependencies": {
"@calcom/dayjs": "*",
"@calcom/lib": "*",
"@calcom/ui": "*",
"@lexical/react": "^0.5.0",
"dompurify": "^2.4.1",
"lexical": "^0.5.0",
"zustand": "^4.1.4",
"@calcom/ui": "*",
"@calcom/lib": "*",
"@calcom/dayjs": "*"
"zustand": "^4.1.4"
}
}

View File

@ -1,5 +1,5 @@
import type { PlaywrightTestConfig } from "@playwright/test";
import { devices } from "@playwright/test";
import type { Frame, PlaywrightTestConfig } from "@playwright/test";
import { devices, expect } from "@playwright/test";
import dotEnv from "dotenv";
import * as os from "os";
import * as path from "path";
@ -21,6 +21,7 @@ const DEFAULT_TEST_TIMEOUT = process.env.CI ? 60000 : 120000;
const headless = !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS;
const IS_EMBED_TEST = process.argv.some((a) => a.startsWith("--project=@calcom/embed-core"));
const IS_EMBED_REACT_TEST = process.argv.some((a) => a.startsWith("--project=@calcom/embed-react"));
const webServer: PlaywrightTestConfig["webServer"] = [
{
@ -33,13 +34,22 @@ const webServer: PlaywrightTestConfig["webServer"] = [
if (IS_EMBED_TEST) {
webServer.push({
command: "yarn workspace @calcom/embed-core run-p 'embed-dev' 'embed-web-start'",
command: "yarn workspace @calcom/embed-core dev",
port: 3100,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
});
}
if (IS_EMBED_REACT_TEST) {
webServer.push({
command: "yarn workspace @calcom/embed-react dev",
port: 3101,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
});
}
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: 2,
@ -90,8 +100,14 @@ const config: PlaywrightTestConfig = {
},
{
name: "@calcom/embed-core",
testDir: "./packages/embeds/",
testMatch: /.*\.e2e\.tsx?/,
testDir: "./packages/embeds/embed-core/",
testMatch: /.*\.(e2e|test)\.tsx?/,
use: { ...devices["Desktop Chrome"] },
},
{
name: "@calcom/embed-react",
testDir: "./packages/embeds/embed-react/",
testMatch: /.*\.(e2e|test)\.tsx?/,
use: { ...devices["Desktop Chrome"] },
},
{
@ -109,4 +125,124 @@ const config: PlaywrightTestConfig = {
],
};
export type ExpectedUrlDetails = {
searchParams?: Record<string, string | string[]>;
pathname?: string;
origin?: string;
};
expect.extend({
async toBeEmbedCalLink(
iframe: Frame,
calNamespace: string,
//TODO: Move it to testUtil, so that it doesn't need to be passed
// eslint-disable-next-line
getActionFiredDetails: (a: { calNamespace: string; actionType: string }) => Promise<any>,
expectedUrlDetails: ExpectedUrlDetails = {}
) {
if (!iframe || !iframe.url) {
return {
pass: false,
message: () => `Expected to provide an iframe, got ${iframe}`,
};
}
const u = new URL(iframe.url());
const frameElement = await iframe.frameElement();
if (!(await frameElement.isVisible())) {
return {
pass: false,
message: () => `Expected iframe to be visible`,
};
}
const pathname = u.pathname;
const expectedPathname = expectedUrlDetails.pathname + "/embed";
if (expectedPathname && expectedPathname !== pathname) {
return {
pass: false,
message: () => `Expected pathname to be ${expectedPathname} but got ${pathname}`,
};
}
const origin = u.origin;
const expectedOrigin = expectedUrlDetails.origin;
if (expectedOrigin && expectedOrigin !== origin) {
return {
pass: false,
message: () => `Expected origin to be ${expectedOrigin} but got ${origin}`,
};
}
const searchParams = u.searchParams;
const expectedSearchParams = expectedUrlDetails.searchParams || {};
for (const [expectedKey, expectedValue] of Object.entries(expectedSearchParams)) {
const value = searchParams.get(expectedKey);
if (value !== expectedValue) {
return {
message: () => `${expectedKey} should have value ${expectedValue} but got value ${value}`,
pass: false,
};
}
}
let iframeReadyCheckInterval;
const iframeReadyEventDetail = await new Promise(async (resolve) => {
iframeReadyCheckInterval = setInterval(async () => {
const iframeReadyEventDetail = await getActionFiredDetails({
calNamespace,
actionType: "linkReady",
});
if (iframeReadyEventDetail) {
resolve(iframeReadyEventDetail);
}
}, 500);
});
clearInterval(iframeReadyCheckInterval);
//At this point we know that window.initialBodyVisibility would be set as DOM would already have been ready(because linkReady event can only fire after that)
const {
visibility: visibilityBefore,
background: backgroundBefore,
initialValuesSet,
} = await iframe.evaluate(() => {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
visibility: window.initialBodyVisibility,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
background: window.initialBodyBackground,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
initialValuesSet: window.initialValuesSet,
};
});
expect(initialValuesSet).toBe(true);
expect(visibilityBefore).toBe("hidden");
expect(backgroundBefore).toBe("transparent");
const { visibility: visibilityAfter, background: backgroundAfter } = await iframe.evaluate(() => {
return {
visibility: document.body.style.visibility,
background: document.body.style.background,
};
});
expect(visibilityAfter).toBe("visible");
expect(backgroundAfter).toBe("transparent");
if (!iframeReadyEventDetail) {
return {
pass: false,
message: () => `Iframe not ready to communicate with parent`,
};
}
return {
pass: true,
message: () => `passed`,
};
},
});
export default config;

68885
yarn.lock

File diff suppressed because it is too large Load Diff