Skip to content

Add a Playwright e2e test #460

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
79 changes: 79 additions & 0 deletions .github/workflows/e2e_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Playwright Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
timeout-minutes: 5
runs-on: ubuntu-latest

steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwoff1

- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 18

# Cache Playwright browsers
- name: Cache Playwright browsers
id: cache-playwright
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright # The default Playwright cache path
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} # Cache key based on OS and package-lock.json
restore-keys: |
${{ runner.os }}-playwright-

- name: Install dependencies
run: npm ci

- name: Install Playwright dependencies
run: npx playwright install-deps

- name: Install Playwright and browsers unless cached
run: npx playwright install --with-deps
if: steps.cache-playwright.outputs.cache-hit != 'true'

- name: Start Server
run: npm run start &

- name: Wait for server to start
run: npx wait-on http://localhost:6274

- name: Run Playwright tests
run: npm run test:e2e

- name: Upload Playwright Report and Screenshots
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: |
client/playwright-report/
client/test-results/
client/results.json
retention-days: 30

- name: Publish Playwright Test Summary
uses: daun/playwright-report-summary@v3
if: always()
with:
report-file: client/results.json
comment-title: "🎭 Playwright E2E Test Results"
job-summary: true
icon-style: "emojis"
custom-info: |
**Test Environment:** Ubuntu Latest, Node.js 18
**Browsers:** Chromium, Firefox

📊 [View Detailed HTML Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) (download artifacts)
test-command: "npm run test:e2e"
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Thanks for your interest in contributing! This guide explains how to get involve

1. Create a new branch for your changes
2. Make your changes following existing code style and conventions. You can run `npm run prettier-check` and `npm run prettier-fix` as applicable.
3. Test changes locally by running `npm test`
3. Test changes locally by running `npm test` and `npm run test:e2e`
4. Update documentation as needed
5. Use clear commit messages explaining your changes
6. Verify all changes work as expected
Expand Down
113 changes: 113 additions & 0 deletions client/e2e/transport-type-dropdown.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { test, expect } from "@playwright/test";

// Adjust the URL if your dev server runs on a different port
const APP_URL = "http://localhost:6274/";

test.describe("Transport Type Dropdown", () => {
test("should have options for STDIO, SSE, and Streamable HTTP", async ({
page,
}) => {
await page.goto(APP_URL);

// Wait for the Transport Type dropdown to be visible
const selectTrigger = page.getByLabel("Transport Type");
await expect(selectTrigger).toBeVisible();

// Open the dropdown
await selectTrigger.click();

// Check for the three options
await expect(page.getByRole("option", { name: "STDIO" })).toBeVisible();
await expect(page.getByRole("option", { name: "SSE" })).toBeVisible();
await expect(
page.getByRole("option", { name: "Streamable HTTP" }),
).toBeVisible();
});

test("should show Command and Arguments fields and hide URL field when Transport Type is STDIO", async ({
page,
}) => {
await page.goto(APP_URL);

// Wait for the Transport Type dropdown to be visible
const selectTrigger = page.getByLabel("Transport Type");
await expect(selectTrigger).toBeVisible();

// Open the dropdown and select STDIO
await selectTrigger.click();
await page.getByRole("option", { name: "STDIO" }).click();

// Wait for the form to update
await page.waitForTimeout(100);

// Check that Command and Arguments fields are visible
await expect(page.locator("#command-input")).toBeVisible();
await expect(page.locator("#arguments-input")).toBeVisible();

// Check that URL field is not visible
await expect(page.locator("#sse-url-input")).not.toBeVisible();

// Also verify the labels are present
await expect(page.getByText("Command")).toBeVisible();
await expect(page.getByText("Arguments")).toBeVisible();
await expect(page.getByText("URL")).not.toBeVisible();
});

test("should show URL field and hide Command and Arguments fields when Transport Type is SSE", async ({
page,
}) => {
await page.goto(APP_URL);

// Wait for the Transport Type dropdown to be visible
const selectTrigger = page.getByLabel("Transport Type");
await expect(selectTrigger).toBeVisible();

// Open the dropdown and select SSE
await selectTrigger.click();
await page.getByRole("option", { name: "SSE" }).click();

// Wait for the form to update
await page.waitForTimeout(100);

// Check that URL field is visible
await expect(page.locator("#sse-url-input")).toBeVisible();

// Check that Command and Arguments fields are not visible
await expect(page.locator("#command-input")).not.toBeVisible();
await expect(page.locator("#arguments-input")).not.toBeVisible();

// Also verify the labels are present/absent
await expect(page.getByText("URL")).toBeVisible();
await expect(page.getByText("Command")).not.toBeVisible();
await expect(page.getByText("Arguments")).not.toBeVisible();
});

test("should show URL field and hide Command and Arguments fields when Transport Type is Streamable HTTP", async ({
page,
}) => {
await page.goto(APP_URL);

// Wait for the Transport Type dropdown to be visible
const selectTrigger = page.getByLabel("Transport Type");
await expect(selectTrigger).toBeVisible();

// Open the dropdown and select Streamable HTTP
await selectTrigger.click();
await page.getByRole("option", { name: "Streamable HTTP" }).click();

// Wait for the form to update
await page.waitForTimeout(100);

// Check that URL field is visible
await expect(page.locator("#sse-url-input")).toBeVisible();

// Check that Command and Arguments fields are not visible
await expect(page.locator("#command-input")).not.toBeVisible();
await expect(page.locator("#arguments-input")).not.toBeVisible();

// Also verify the labels are present/absent
await expect(page.getByText("URL")).toBeVisible();
await expect(page.getByText("Command")).not.toBeVisible();
await expect(page.getByText("Arguments")).not.toBeVisible();
});
});
2 changes: 2 additions & 0 deletions client/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ module.exports = {
"/node_modules/",
"/dist/",
"/bin/",
"/e2e/",
"\\.config\\.(js|ts|cjs|mjs)$",
],
// Exclude the same patterns from coverage reports
coveragePathIgnorePatterns: [
"/node_modules/",
"/dist/",
"/bin/",
"/e2e/",
"\\.config\\.(js|ts|cjs|mjs)$",
],
};
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"lint": "eslint .",
"preview": "vite preview --port 6274",
"test": "jest --config jest.config.cjs",
"test:watch": "jest --config jest.config.cjs --watch"
"test:watch": "jest --config jest.config.cjs --watch",
"test:e2e": "playwright test e2e"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
Expand Down
86 changes: 86 additions & 0 deletions client/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { defineConfig, devices } from "@playwright/test";

/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: "./e2e",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
["html", { outputFolder: "playwright-report" }],
["json", { outputFile: "results.json" }],
["line"],
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:6274",

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",

/* Take screenshots on failure */
screenshot: "only-on-failure",

/* Record video on failure */
video: "retain-on-failure",
},

/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},

{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},

// Skip WebKit on macOS due to compatibility issues
...(process.platform !== "darwin"
? [
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
]
: []),

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:6274',
// reuseExistingServer: !process.env.CI,
// },
});
Loading
Loading