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
2 changes: 2 additions & 0 deletions .moon/tasks/tag-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ tasks:
- '@group(extension-dist)'
extension-build-firefox:
toolchain: node
options:
runInCI: false
script: >-
$workspaceRoot/etc/scripts/actions/extension-build-firefox.ts
env:
Expand Down
2 changes: 2 additions & 0 deletions .moon/tasks/tag-playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ fileGroups:
tasks:
playwright-test:
toolchain: node
options:
outputStyle: stream
env:
PLAYWRIGHT_BROWSERS_PATH: $workspaceRoot/.tmp/playwright/browsers
NODE_OPTIONS: "--require @swc-node/register"
Expand Down
1 change: 1 addition & 0 deletions .moon/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ vcs:
manager: git
defaultBranch: main
provider: github
syncHooks: true
hooks:
pre-commit:
- moon scripts:check
Expand Down
1 change: 1 addition & 0 deletions apps/ext-e2e/.swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
}



2 changes: 1 addition & 1 deletion apps/ext-e2e/package.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"name": "@mcp-browser-kit/ext-e2e"
"name": "@mcp-browser-kit/ext-e2e"
}
9 changes: 8 additions & 1 deletion apps/ext-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ export default defineConfig<ExtContextOptions>({
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [["html", { outputFolder: "target/playwright/playwright-report" }]],
reporter: [
[
"html",
{
outputFolder: "target/playwright/playwright-report",
},
],
],
use: {
trace: "on",
video: "on",
Expand Down
109 changes: 64 additions & 45 deletions apps/ext-e2e/src/fixtures/ext-context.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,76 @@
import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
import path from "node:path";
import { type BrowserContext, test as base, chromium } from "@playwright/test";

export type ExtTarget = 'm2' | 'm3';
export type ExtTarget = "m2" | "m3";

function getExtensionPath(target: ExtTarget): string {
const basePath = path.join(__dirname, '..', '..', '..', target, 'target', 'extension', 'tmp', 'extension');
return basePath;
const basePath = path.join(
__dirname,
"..",
"..",
"..",
target,
"target",
"extension",
"tmp",
"extension",
);
return basePath;
}

export type ExtContextFixtures = {
context: BrowserContext;
extensionId: string;
context: BrowserContext;
extensionId: string;
};

export type ExtContextOptions = {
extTarget: ExtTarget;
extTarget: ExtTarget;
};

export const extContextTest = base.extend<ExtContextFixtures, ExtContextOptions>({
extTarget: ['m3', { option: true, scope: 'worker' }],

context: async ({ extTarget, launchOptions }, use) => {
const pathToExtension = getExtensionPath(extTarget);

const context = await chromium.launchPersistentContext('', {
...launchOptions,
channel: 'chromium',
args: [
...(launchOptions.args ?? []),
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});

await use(context);
await context.close();
},

extensionId: async ({ context, extTarget }, use) => {
if (extTarget === 'm3') {
let [serviceWorker] = context.serviceWorkers();
if (!serviceWorker) {
serviceWorker = await context.waitForEvent('serviceworker');
}
const extensionId = serviceWorker.url().split('/')[2];
await use(extensionId);
} else {
let [backgroundPage] = context.backgroundPages();
if (!backgroundPage) {
backgroundPage = await context.waitForEvent('backgroundpage');
}
const extensionId = backgroundPage.url().split('/')[2];
await use(extensionId);
}
},
export const extContextTest = base.extend<
ExtContextFixtures,
ExtContextOptions
>({
extTarget: [
"m3",
{
option: true,
scope: "worker",
},
],

context: async ({ extTarget, launchOptions }, use) => {
const pathToExtension = getExtensionPath(extTarget);

const context = await chromium.launchPersistentContext("", {
...launchOptions,
channel: "chromium",
args: [
...(launchOptions.args ?? []),
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});

await use(context);
await context.close();
},

extensionId: async ({ context, extTarget }, use) => {
if (extTarget === "m3") {
let [serviceWorker] = context.serviceWorkers();
if (!serviceWorker) {
serviceWorker = await context.waitForEvent("serviceworker");
}
const extensionId = serviceWorker.url().split("/")[2];
await use(extensionId);
} else {
let [backgroundPage] = context.backgroundPages();
if (!backgroundPage) {
backgroundPage = await context.waitForEvent("backgroundpage");
}
const extensionId = backgroundPage.url().split("/")[2];
await use(extensionId);
}
},
});
45 changes: 26 additions & 19 deletions apps/ext-e2e/src/fixtures/ext-test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { extContextTest, type ExtContextFixtures, type ExtContextOptions } from './ext-context';
import { PlaywrightPage } from '../pages/playwright-page';
import { McpClientPageObject } from '../pages/mcp-client-page-object';
import { McpClientPageObject } from "../pages/mcp-client-page-object";
import { PlaywrightPage } from "../pages/playwright-page";
import {
type ExtContextFixtures,
type ExtContextOptions,
extContextTest,
} from "./ext-context";

export type { ExtContextOptions };

export type ExtTestFixtures = ExtContextFixtures & ExtContextOptions & {
playwrightPage: PlaywrightPage;
mcpClientPage: McpClientPageObject;
};
export type ExtTestFixtures = ExtContextFixtures &
ExtContextOptions & {
playwrightPage: PlaywrightPage;
mcpClientPage: McpClientPageObject;
};

export const test = extContextTest.extend<Omit<ExtTestFixtures, keyof ExtContextFixtures>>({
playwrightPage: async ({ context }, use) => {
const page = await context.newPage();
const playwrightPage = new PlaywrightPage(page);
await use(playwrightPage);
},
// biome-ignore lint/correctness/noEmptyPattern: Playwright requires object destructuring pattern
mcpClientPage: async ({}, use) => {
const mcpClientPage = new McpClientPageObject();
await use(mcpClientPage);
},
export const test = extContextTest.extend<
Omit<ExtTestFixtures, keyof ExtContextFixtures>
>({
playwrightPage: async ({ context }, use) => {
const page = await context.newPage();
const playwrightPage = new PlaywrightPage(page);
await use(playwrightPage);
},
// biome-ignore lint/correctness/noEmptyPattern: Playwright requires object destructuring pattern
mcpClientPage: async ({}, use) => {
const mcpClientPage = new McpClientPageObject();
await use(mcpClientPage);
},
});

export { expect } from '@playwright/test';
export { expect } from "@playwright/test";
74 changes: 37 additions & 37 deletions apps/ext-e2e/src/pages/base-page.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import { Page, Locator } from '@playwright/test';
import type { Locator, Page } from "@playwright/test";

export abstract class BasePage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async goto(url: string) {
await this.page.goto(url);
}

async waitForPageLoad() {
await this.page.waitForLoadState('networkidle');
}

async getTitle(): Promise<string> {
return await this.page.title();
}

protected getLocator(selector: string): Locator {
return this.page.locator(selector);
}

protected getByRole(role: Parameters<Page['getByRole']>[0], options?: Parameters<Page['getByRole']>[1]): Locator {
return this.page.getByRole(role, options);
}

protected getByText(text: string | RegExp): Locator {
return this.page.getByText(text);
}

protected getByTestId(testId: string): Locator {
return this.page.getByTestId(testId);
}
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async goto(url: string) {
await this.page.goto(url);
}

async waitForPageLoad() {
await this.page.waitForLoadState("networkidle");
}

async getTitle(): Promise<string> {
return await this.page.title();
}

protected getLocator(selector: string): Locator {
return this.page.locator(selector);
}

protected getByRole(
role: Parameters<Page["getByRole"]>[0],
options?: Parameters<Page["getByRole"]>[1],
): Locator {
return this.page.getByRole(role, options);
}

protected getByText(text: string | RegExp): Locator {
return this.page.getByText(text);
}

protected getByTestId(testId: string): Locator {
return this.page.getByTestId(testId);
}
}



1 change: 0 additions & 1 deletion apps/ext-e2e/src/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { BasePage } from "./base-page";
export { McpClientPageObject } from "./mcp-client-page-object";
export { PlaywrightPage } from "./playwright-page";

26 changes: 17 additions & 9 deletions apps/ext-e2e/src/pages/mcp-client-page-object.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import type {
ServerToolArgs,
ServerToolName,
ServerToolResult,
} from "@mcp-browser-kit/core-server";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import {
ListToolsResultSchema,
type CallToolResult,
CallToolResultSchema,
ListResourcesResultSchema,
type CallToolResult,
ListToolsResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import type {
ServerToolName,
ServerToolArgs,
ServerToolResult,
} from "@mcp-browser-kit/core-server";

export type TypedCallToolResult<T extends ServerToolName> = Omit<
CallToolResult,
"structuredContent"
> & {
structuredContent?: ServerToolResult<T>;
};

import {
createCoreServerContainer,
LoggerFactoryOutputPort,
Expand Down Expand Up @@ -134,7 +135,14 @@ export class McpClientPageObject {
const { expect } = await import("@playwright/test");
await expect(async () => {
const contextOutput = await this.callTool("getContext", {});
expect(contextOutput.structuredContent?.browsers?.length).toBeGreaterThan(0);
}).toPass({ timeout, intervals: [2000] });
expect(contextOutput.structuredContent?.browsers?.length).toBeGreaterThan(
0,
);
}).toPass({
timeout,
intervals: [
2000,
],
});
}
}
Loading