Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/platform/examples/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"dotenv": "^17.2.1",
"eslint": "^8.34.0",
"eslint-config-next": "14.0.4",
"postcss": "^8",
Expand Down
6 changes: 6 additions & 0 deletions packages/platform/examples/base/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { defineConfig, devices } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";

const envPath = process.env.CI ? path.resolve(__dirname, ".env") : path.resolve(__dirname, ".env.local");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In prod we would not have an .env file - all of the variables are already in CI environment but its fine - lets just get this merged and I have started refactoring e2e setup locally with separate test database, database cleanup etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI there should be env.local comitted to this PR containing all the keys necessary for the local e2e test and then dev fills them manually.

usually you call it env.local.example and commit it and then in gitignore ignore env.local because you rename env.local.example to env.local and fill necessary keys.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its fine for now as longs as e2e in ci pass because i will fix it in separate PR


dotenv.config({ path: envPath });

const DEFAULT_EXPECT_TIMEOUT = process.env.CI ? 30000 : 120000;
const DEFAULT_TEST_TIMEOUT = process.env.CI ? 60000 : 240000;
Expand Down
2 changes: 1 addition & 1 deletion packages/platform/examples/base/src/pages/calendars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function Calendars(props: { calUsername: string; calEmail: string
return (
<main className={`flex min-h-screen flex-col ${inter.className}`}>
<Navbar username={props.calUsername} />
<div>
<div data-testid="calendars-settings-atom">
<CalendarSettings
allowDelete={true}
classNames={{
Expand Down
9 changes: 8 additions & 1 deletion packages/platform/examples/base/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Navbar } from "@/components/Navbar";
import { Inter, Poppins } from "next/font/google";
// eslint-disable-next-line @calcom/eslint/deprecated-imports-next-router
import { useRouter } from "next/router";

import { Connect, StripeConnect } from "@calcom/atoms";

const inter = Inter({ subsets: ["latin"] });
const poppins = Poppins({ subsets: ["latin"], weight: ["400", "800"] });

export default function Home(props: { calUsername: string; calEmail: string }) {
const router = useRouter();

return (
<main className={`flex min-h-screen flex-col ${inter.className} items-center justify-center`}>
<Navbar username={props.calUsername} />
Expand All @@ -19,7 +23,7 @@ export default function Home(props: { calUsername: string; calEmail: string }) {
<p className={`w-[70%] font-normal ${inter.className} pb-3 text-2xl`}>
To get started, connect your google calendar.
</p>
<div className="flex flex-row gap-4">
<div data-testid="connect-atoms" className="flex flex-row gap-4">
<Connect.GoogleCalendar
redir="http://localhost:4321/calendars"
className="h-[40px] bg-gradient-to-r from-[#8A2387] via-[#E94057] to-[#F27121] text-center text-base font-semibold text-transparent text-white hover:bg-orange-700"
Expand All @@ -30,6 +34,9 @@ export default function Home(props: { calUsername: string; calEmail: string }) {
className="h-[40px] bg-gradient-to-r from-[#8A2387] via-[#E94057] to-[#F27121] text-center text-base font-semibold text-transparent text-white hover:bg-orange-700"
/>
<Connect.AppleCalendar
onSuccess={() => {
router.push(`/calendars`);
}}
isMultiCalendar={true}
className="h-[40px] bg-gradient-to-r from-[#8A2387] via-[#E94057] to-[#F27121] text-center text-base font-semibold text-transparent text-white hover:bg-orange-700"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { test, expect } from "@playwright/test";

test("connect calendar using the apple connect atom", async ({ page }) => {
const appleId = process.env.ATOMS_E2E_APPLE_ID;
const appSpecificPassword = process.env.ATOMS_E2E_APPLE_CONNECT_APP_SPECIFIC_PASSCODE;

await page.goto("/");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm Playwright baseURL is set for the examples app.

Without baseURL, page.goto("/") will fail locally/CI.

Run to verify:


🏁 Script executed:

#!/bin/bash
# Check baseURL and dotenv loading in Playwright config
fd -a 'playwright.config.ts' | xargs -I{} sh -c 'echo "== {} =="; rg -n "baseURL|dotenv|config\\(" {} -n -C2'

# Ensure APPLE_* appears in turbo.json globalEnv and .env.example files
fd -a 'turbo.json' | xargs -I{} sh -c 'echo "== {} =="; cat "{}" | jq .globalEnv'
fd -a '.env.example' | xargs -I{} sh -c 'echo "== {} =="; rg -n "APPLE_ID|APPLE_CONNECT_APP_SPECIFIC_PASSCODE" "{}" -n'

Length of output: 9199


Playwright baseURL is configured; add Apple credential placeholders to the example’s .env.example

  • packages/platform/examples/base/playwright.config.ts already sets baseURL: "http://localhost:4322", so await page.goto("/") will work as expected.
  • packages/platform/examples/base/.env.example is missing APPLE_ID and APPLE_CONNECT_APP_SPECIFIC_PASSCODE entries—add these so developers know to supply the credentials when running the Apple-connect E2E tests.
🤖 Prompt for AI Agents
In packages/platform/examples/base/tests/connect-atoms/apple-connect.e2e.ts
around line 7, the test uses await page.goto("/") relying on playwright baseURL
but the example’s .env.example is missing Apple credential placeholders; update
packages/platform/examples/base/.env.example to add two commented placeholder
entries APPLE_ID=your_apple_id and
APPLE_CONNECT_APP_SPECIFIC_PASSCODE=your_app_specific_passcode (or similar names
matching test env usage) with brief comments so developers know to supply those
credentials when running the Apple-connect E2E tests.


await expect(page.locator("body")).toBeVisible();

await expect(page.locator('[data-testid="connect-atoms"]')).toBeVisible();

await page.locator('[data-testid="connect-atoms"] button:has-text("Connect Apple Calendar")').click();

await expect(page.locator('[role="dialog"]')).toBeVisible();

await expect(page.locator('[role="dialog"] fieldset[data-testid="apple-calendar-form"]')).toBeVisible();

await expect(page.locator('[data-testid="apple-calendar-email"]')).toBeVisible();
await page.locator('[data-testid="apple-calendar-email"]').fill(appleId ?? "");

await expect(page.locator('[data-testid="apple-calendar-password"]')).toBeVisible();
await page.locator('[data-testid="apple-calendar-password"]').fill(appSpecificPassword ?? "");

await page.locator('[data-testid="apple-calendar-login-button"]').click();

await expect(page).toHaveURL("/calendars");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix brittle URL assertion (exact-match vs full URL).

Use regex/suffix to avoid strict equality mismatch with baseURL.

-  await expect(page).toHaveURL("/calendars");
+  await expect(page).toHaveURL(/\/calendars$/);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await expect(page).toHaveURL("/calendars");
await expect(page).toHaveURL(/\/calendars$/);
🤖 Prompt for AI Agents
In packages/platform/examples/base/tests/connect-atoms/apple-connect.e2e.ts
around line 27, the test uses a brittle exact URL equality; replace the strict
equality assertion with a pattern match (regex or suffix match) that only
asserts the path ends with "/calendars" (allowing for full baseURL, query
strings or trailing slashes) so the test passes regardless of baseURL
differences.


await expect(page.locator("body")).toBeVisible();

await expect(page.locator('[data-testid="calendars-settings-atom"]')).toBeVisible();

await expect(page.locator('h2:has-text("Add to calendar")')).toBeVisible();

await expect(page.locator('label:has-text("Add events to")')).toBeVisible();

await expect(page.locator('[data-testid="select-control"]')).toBeVisible();
await page.locator('[data-testid="select-control"]').click();

await page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");

Comment on lines +37 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make select interaction deterministic.

Wait for listbox and pick a concrete option instead of key presses.

-  await expect(page.locator('[data-testid="select-control"]')).toBeVisible();
-  await page.locator('[data-testid="select-control"]').click();
-
-  await page.keyboard.press("ArrowDown");
-  await page.keyboard.press("Enter");
+  await expect(page.getByTestId("select-control")).toBeVisible();
+  await page.getByTestId("select-control").click();
+  await expect(page.getByRole("listbox")).toBeVisible();
+  await page.getByRole("option").first().click();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await expect(page.locator('[data-testid="select-control"]')).toBeVisible();
await page.locator('[data-testid="select-control"]').click();
await page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");
await expect(page.getByTestId("select-control")).toBeVisible();
await page.getByTestId("select-control").click();
await expect(page.getByRole("listbox")).toBeVisible();
await page.getByRole("option").first().click();
🤖 Prompt for AI Agents
In packages/platform/examples/base/tests/connect-atoms/apple-connect.e2e.ts
around lines 37 to 42, the test uses keyboard ArrowDown/Enter which is
non-deterministic; instead wait for the listbox element to appear after opening
the select and click a specific option directly. Replace the keyboard presses
with: await page.locator('[role="listbox"]').waitFor({ state: 'visible' }); then
find the concrete option by text or index (e.g. await
page.locator('[role="option"]', { hasText: 'Option Label' }).click() or await
page.locator('[role="option"]').nth(index).click()) and await its
visibility/click to ensure deterministic selection.

await expect(page.locator('h4:has-text("Check for conflicts")')).toBeVisible();

await expect(page.locator('[data-testid="list"]')).toBeVisible();
await page.locator('[data-testid="list"] button:has(svg[data-name="start-icon"])').click();
await page.locator('[data-testid="dialog-rejection"]').click();
Comment on lines +46 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Await dialog visibility before acting on it.

Eliminates race conditions.

-  await page.locator('[data-testid="list"] button:has(svg[data-name="start-icon"])').click();
-  await page.locator('[data-testid="dialog-rejection"]').click();
+  await page.locator('[data-testid="list"] button:has(svg[data-name="start-icon"])').click();
+  await expect(page.locator('[data-testid="dialog-rejection"]')).toBeVisible();
+  await page.locator('[data-testid="dialog-rejection"]').click();
@@
-  await page.locator('[data-testid="list"] button:has(svg[data-name="start-icon"])').click();
-  await page.locator('[data-testid="dialog-confirmation"]').click();
+  await page.locator('[data-testid="list"] button:has(svg[data-name="start-icon"])').click();
+  await expect(page.locator('[data-testid="dialog-confirmation"]')).toBeVisible();
+  await page.locator('[data-testid="dialog-confirmation"]').click();

Also applies to: 58-59

🤖 Prompt for AI Agents
In packages/platform/examples/base/tests/connect-atoms/apple-connect.e2e.ts
around lines 46-47 (and similarly for lines 58-59), the test clicks the dialog
rejection button immediately which can race with the dialog appearing; wait for
the dialog to be visible before interacting with it. Modify the test to await
the dialog locator being visible (e.g. waitForSelector or locator.waitFor with
state 'visible') prior to calling click, and apply the same change to the other
occurrence on lines 58-59.


await expect(page.locator('[data-testid="list"] button[role="switch"]').first()).toBeVisible();
await page.locator('[data-testid="list"] button[role="switch"]').first().click();

await expect(page.locator('[data-testid="list"] button[role="switch"]').last()).toBeVisible();
await page.locator('[data-testid="list"] button[role="switch"]').last().click();

await expect(page.locator('[data-testid="list"] button[role="switch"]').first()).toBeVisible();
await page.locator('[data-testid="list"] button[role="switch"]').first().click();
Comment on lines +49 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Assert switch state changes, not just clicks.

Validates behavior via aria-checked.

-  await expect(page.locator('[data-testid="list"] button[role="switch"]').first()).toBeVisible();
-  await page.locator('[data-testid="list"] button[role="switch"]').first().click();
-
-  await expect(page.locator('[data-testid="list"] button[role="switch"]').last()).toBeVisible();
-  await page.locator('[data-testid="list"] button[role="switch"]').last().click();
-
-  await expect(page.locator('[data-testid="list"] button[role="switch"]').first()).toBeVisible();
-  await page.locator('[data-testid="list"] button[role="switch"]').first().click();
+  const list = page.getByTestId("list");
+  const firstSwitch = list.getByRole("switch").first();
+  await expect(firstSwitch).toBeVisible();
+  const prevFirst = await firstSwitch.getAttribute("aria-checked");
+  await firstSwitch.click();
+  await expect(firstSwitch).toHaveAttribute("aria-checked", prevFirst === "true" ? "false" : "true");
+
+  const lastSwitch = list.getByRole("switch").last();
+  await expect(lastSwitch).toBeVisible();
+  const prevLast = await lastSwitch.getAttribute("aria-checked");
+  await lastSwitch.click();
+  await expect(lastSwitch).toHaveAttribute("aria-checked", prevLast === "true" ? "false" : "true");
+
+  await firstSwitch.click(); // revert
+  await expect(firstSwitch).toHaveAttribute("aria-checked", prevFirst);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await expect(page.locator('[data-testid="list"] button[role="switch"]').first()).toBeVisible();
await page.locator('[data-testid="list"] button[role="switch"]').first().click();
await expect(page.locator('[data-testid="list"] button[role="switch"]').last()).toBeVisible();
await page.locator('[data-testid="list"] button[role="switch"]').last().click();
await expect(page.locator('[data-testid="list"] button[role="switch"]').first()).toBeVisible();
await page.locator('[data-testid="list"] button[role="switch"]').first().click();
const list = page.getByTestId("list");
const firstSwitch = list.getByRole("switch").first();
await expect(firstSwitch).toBeVisible();
const prevFirst = await firstSwitch.getAttribute("aria-checked");
await firstSwitch.click();
await expect(firstSwitch).toHaveAttribute("aria-checked", prevFirst === "true" ? "false" : "true");
const lastSwitch = list.getByRole("switch").last();
await expect(lastSwitch).toBeVisible();
const prevLast = await lastSwitch.getAttribute("aria-checked");
await lastSwitch.click();
await expect(lastSwitch).toHaveAttribute("aria-checked", prevLast === "true" ? "false" : "true");
await firstSwitch.click(); // revert
await expect(firstSwitch).toHaveAttribute("aria-checked", prevFirst);
🤖 Prompt for AI Agents
In packages/platform/examples/base/tests/connect-atoms/apple-connect.e2e.ts
around lines 49 to 56, the test only clicks the switch elements without
asserting their state change; update the test to assert the switch's
aria-checked state after each interaction (use
locator.getAttribute('aria-checked') or Playwright's toHaveAttribute matcher) to
confirm toggling (e.g., check aria-checked flips from "false" to "true" after
click and back after subsequent click) so each click is followed by an explicit
assertion of the new state.


await page.locator('[data-testid="list"] button:has(svg[data-name="start-icon"])').click();
await page.locator('[data-testid="dialog-confirmation"]').click();
});
2 changes: 2 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@
"NEXT_PUBLIC_WEBAPP_URL",
"NEXT_PUBLIC_WEBSITE_URL",
"BUILD_STANDALONE",
"ATOMS_E2E_APPLE_ID",
"ATOMS_E2E_APPLE_CONNECT_APP_SPECIFIC_PASSCODE",
"INTERCOM_API_TOKEN",
"NEXT_PUBLIC_INTERCOM_APP_ID"
],
Expand Down
Loading