Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ jobs:
# todo(nickbar01234): Cache playwright?
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps

- name: Start Firebase emulator
Copy link
Owner

Choose a reason for hiding this comment

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

Cool that this just works

run: docker compose up -d
- name: Wait for firebase emulator
run: |
echo "Waiting to check firebase to be healthy"
until [ "$(docker inspect --format='{{.State.Health.Status}}' firebase)" = "healthy" ]; do
echo "Waiting..."
sleep 2
done
echo "Firebase is healthy"
# todo(nickbar01234): Should switch to chrome-mv3 for production build or setup CI to point to docker firebase
- name: Build extension in dev mode
run: pnpm build:dev
Expand Down
2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.8",
Expand Down
1 change: 1 addition & 0 deletions extension/src/components/panel/editor/EditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ const EditorPanel = () => {
trigger={{
node: (
<div
data-testid="toggle-code-visibility"
className="h-fit hover:bg-fill-quaternary dark:hover:bg-fill-quaternary inline-flex items-center justify-between focus:outline-none p-2 rounded-md cursor-pointer"
onClick={toggleCodeVisibility}
>
Expand Down
2 changes: 1 addition & 1 deletion extension/src/components/panel/editor/tab/CodeTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const CodeTab: React.FC = () => {
className="h-fit hover:bg-fill-quaternary dark:hover:bg-fill-quaternary inline-flex items-center justify-between focus:outline-none p-2 rounded-md cursor-pointer"
onClick={copyCode}
>
<Copy size={16} />
<Copy data-testid="copy-code" size={16} />
</div>
),
}}
Expand Down
1 change: 1 addition & 0 deletions extension/src/components/panel/problem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DOM, EXTENSION } from "@cb/constants";
import { useHtmlActions } from "@cb/hooks/store";
import useResource from "@cb/hooks/useResource";
import { Question } from "@cb/types";
import { waitForElement } from "@cb/utils";
import React, { useEffect } from "react";
import { toast } from "sonner";

Expand Down
5 changes: 1 addition & 4 deletions extension/src/components/root/ContentScript.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AppNavigator } from "@cb/components/navigator/AppNavigator";
import { ContainerNavigator } from "@cb/components/navigator/ContainerNavigator";
import { AppControlMenu, RoomControlMenu } from "@cb/components/navigator/menu";
import { BottomBannerPanel } from "@cb/components/panel/BottomBannerPanel";
import { LoadingPanel } from "@cb/components/panel/LoadingPanel";
import { ResizablePanel } from "@cb/components/panel/ResizablePanel";
import SignInPanel from "@cb/components/panel/SignInPanel";
Expand All @@ -22,9 +21,7 @@ export const ContentScript = () => {
case AppStatus.AUTHENTICATED:
return (
<ContainerNavigator menu={<RoomControlMenu />}>
<BottomBannerPanel>
<AppNavigator />
</BottomBannerPanel>
<AppNavigator />
</ContainerNavigator>
);
case AppStatus.UNAUTHENTICATED:
Expand Down
56 changes: 0 additions & 56 deletions extension/tests/fixture.ts

This file was deleted.

49 changes: 49 additions & 0 deletions extension/tests/fixture/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { test as base, BrowserContext, chromium, Page } from "@playwright/test";
import { signIn } from "@tests/utils/auth";
import { getExtensionId, getExtensionPath, setupPage } from "@tests/utils/page";

export interface UserPage {
Copy link
Owner

Choose a reason for hiding this comment

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

nit: Add space above

page: Page;
email: string;
extensionId: string;
context: BrowserContext;
}

interface PlayWrightPageFactory {
instantiate: (email: string) => Promise<UserPage>;
}
const extension = getExtensionPath();

export const factory = base.extend<{ pageCreator: PlayWrightPageFactory }>({
// eslint-disable-next-line no-empty-pattern
pageCreator: async ({}, use) => {
const contexts: BrowserContext[] = [];
const instantiate: PlayWrightPageFactory["instantiate"] = async (email) => {
const context = await chromium.launchPersistentContext("", {
headless: false,
channel: "chromium",
args: [
`--disable-extensions-except=${extension}`,
`--load-extension=${extension}`,
],
permissions: [
"clipboard-read",
"clipboard-write",
"local-network-access",
],
});
contexts.push(context);

const extensionId = await getExtensionId(context);
const page = context.pages()[0] ?? (await context.newPage());
await setupPage(page);
await signIn(page, email);

return { page, context, email, extensionId };
};

await use({ instantiate });

await Promise.all(contexts.map((ctx) => ctx.close()));
Copy link
Owner

Choose a reason for hiding this comment

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

For my understanding, why is contexts a list?

},
});
36 changes: 36 additions & 0 deletions extension/tests/fixture/in-room.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createRoom, joinRoom, RoomInfo } from "@tests/utils/room";
import { factory, UserPage } from "./factory";

interface UserInRoomPage extends UserPage {
room: RoomInfo;
}

export const singleUserTest = factory.extend<{
user: UserInRoomPage;
}>({
user: async ({ pageCreator }, use) => {
const user = await pageCreator.instantiate("user@test.com");
const room = await createRoom(user.page);
await use({ ...user, room });
},
});

export const twoUserTest = factory.extend<{
user1: UserInRoomPage;
user2: UserInRoomPage;
}>({
user1: async ({ pageCreator }, use) => {
const user = await pageCreator.instantiate("user1@test.com");
const room = await createRoom(user.page);
await use({ ...user, room });
},

user2: async ({ pageCreator, user1 }, use) => {
const user = await pageCreator.instantiate("user2@test.com");
await joinRoom(user.page, user1.room.id);
await use({ ...user, room: user1.room });
},
});

export const twoUserExpect = twoUserTest.expect;
export const singleUserExpect = singleUserTest.expect;
24 changes: 19 additions & 5 deletions extension/tests/pages/content.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { expect, test } from "@tests/fixture";
import { twoUserExpect, twoUserTest } from "@tests/fixture/in-room";

test("Content script is mounted", async ({ page }) => {
await expect(page.getByText("CodeBuddy").first()).toBeVisible({
timeout: 30_000,
});
const EXPECTED_CPP_CODE = `class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {

}
};`;

twoUserTest("User1 can copy code from User2", async ({ user1, user2 }) => {

Check warning on line 10 in extension/tests/pages/content.spec.ts

View workflow job for this annotation

GitHub Actions / lint

'user2' is defined but never used
await user1.page.getByRole("tab", { name: /Code/i }).click();
await user1.page.getByTestId("toggle-code-visibility").click();
await user1.page.getByTestId("copy-code").click({ timeout: 30_000 });

await twoUserExpect(async () => {
const copiedCode = await user1.page.evaluate(() =>
navigator.clipboard.readText()
);
twoUserExpect(copiedCode.trim()).toBe(EXPECTED_CPP_CODE.trim());
}).toPass();
});
39 changes: 0 additions & 39 deletions extension/tests/scripts/append-test-case.spec.ts

This file was deleted.

20 changes: 0 additions & 20 deletions extension/tests/scripts/router.spec.ts

This file was deleted.

6 changes: 6 additions & 0 deletions extension/tests/utils/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Page } from "@playwright/test";

export async function signIn(page: Page, email: string): Promise<void> {
await page.getByRole("textbox", { name: "Enter your email" }).fill(email);
await page.getByRole("button", { name: "Continue" }).click();
}
51 changes: 51 additions & 0 deletions extension/tests/utils/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { DOM } from "@cb/constants";
import { chromium, type BrowserContext, type Page } from "@playwright/test";
import fs from "node:fs";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";

const extension = resolve(
dirname(fileURLToPath(import.meta.url)),
"../../dist/chrome-mv3-dev"
);

if (!fs.existsSync(extension)) {
throw new Error(`Invalid path ${extension}`);
}

export function getExtensionPath(): string {
return extension;
}

export async function createExtensionContext(): Promise<BrowserContext> {
const context = await chromium.launchPersistentContext("", {
headless: false,
channel: "chromium",
args: [
`--disable-extensions-except=${extension}`,
`--load-extension=${extension}`,
],
permissions: ["clipboard-read", "clipboard-write", "local-network-access"],
});
return context;
}

export async function getExtensionId(context: BrowserContext): Promise<string> {
let [serviceWorker] = context.serviceWorkers();
if (!serviceWorker)
serviceWorker = await context.waitForEvent("serviceworker");
return serviceWorker.url().split("/")[2];
}

export async function setupPage(page: Page): Promise<void> {
page.on("console", (msg) => {
console.log("Received message from page", msg.text(), msg.type());
});
await page.goto("https://leetcode.com/problems/two-sum", {
waitUntil: "domcontentloaded",
});
await page.waitForSelector(DOM.LEETCODE_ROOT_ID, {
state: "visible",
timeout: 30_000,
});
}
22 changes: 22 additions & 0 deletions extension/tests/utils/room.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Page } from "@playwright/test";

export interface RoomInfo {
id: string;
}

export async function createRoom(page: Page): Promise<RoomInfo> {
await page.getByRole("button", { name: "Create Room" }).click();
await page.getByRole("radio", { name: "Private" }).click();
await page.getByRole("button", { name: "Create" }).click();
await page.getByRole("img", { name: "Copy room ID" }).click();

const roomId = await page.evaluate(() => navigator.clipboard.readText());

return { id: roomId };
}

export async function joinRoom(page: Page, roomId: string): Promise<void> {
await page.getByRole("button", { name: "Join room" }).click();
await page.getByRole("textbox", { name: "Enter room ID" }).fill(roomId);
await page.getByRole("button", { name: "Join" }).click();
}
2 changes: 1 addition & 1 deletion extension/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"@cb/*": ["./src/*"]
}
},
"exclude": ["./**/*.spec.ts", ".output"],
"exclude": ["./**/*.spec.ts", ".output", "tests"],
"extends": ["./.wxt/tsconfig.json"]
}
Loading
Loading