-
Notifications
You must be signed in to change notification settings - Fork 0
Tamdang/e2e/multi users connection #588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
529ae03
14f77a7
d8b4077
7ed84a9
3eae3d0
4148f4d
c425a39
53e0e23
a215e9b
32bea50
ed08181
6093bd5
cf58acf
bd35c83
b750e9d
9e72d61
91ce42d
507e0fd
5c1798e
d228758
70ea487
6c6a27d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| 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 { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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())); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For my understanding, why is contexts a list? |
||
| }, | ||
| }); | ||
| 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; |
| 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 }) => { | ||
| 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(); | ||
| }); | ||
This file was deleted.
This file was deleted.
| 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(); | ||
| } |
| 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, | ||
| }); | ||
| } |
| 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(); | ||
| } |
There was a problem hiding this comment.
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