Skip to content

Commit 5ef61a7

Browse files
patricktreekecrily
andauthored
test: setup E2E testing via Playwright and implement some tests (#88)
Co-authored-by: Percy Ma <[email protected]>
1 parent c9ba1be commit 5ef61a7

18 files changed

+323
-12
lines changed

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,37 @@ jobs:
4848
- name: Check formatting of everything (prettier)
4949
run: |
5050
npm run fmt:check
51+
52+
e2e-test:
53+
name: E2E Tests (Playwright)
54+
timeout-minutes: 60
55+
# run only if triggered by push on a branch or by a PR event for a PR which is not a draft
56+
if: ${{ !github.event.pull_request || github.event.pull_request.draft == false }}
57+
runs-on: ubuntu-24.04
58+
steps:
59+
- uses: actions/checkout@v6
60+
61+
- name: Install Node.js
62+
uses: actions/setup-node@v6
63+
with:
64+
node-version-file: ".nvmrc"
65+
cache: npm
66+
67+
- name: Install dependencies
68+
run: |
69+
npm ci
70+
71+
- name: Install Playwright browsers
72+
run: |
73+
npx playwright install --with-deps
74+
75+
- name: Run Playwright tests
76+
run: |
77+
npm run test:e2e
78+
79+
- uses: actions/upload-artifact@v6
80+
if: ${{ !cancelled() }}
81+
with:
82+
name: playwright-report
83+
path: playwright-report/
84+
retention-days: 30

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,9 @@ npm-debug.log*
2727

2828
# Automatically generated files by GitHub Actions workflow
2929
/.shared-workflows
30+
31+
# Playwright
32+
/test-results/
33+
/playwright-report/
34+
/blob-report/
35+
/playwright/.cache/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ To install and set up the project, follow these steps:
1313
1. Ensure you have Node.js v20 installed. You can download it from the [official Node.js website](https://nodejs.org/).
1414
2. Clone the repository to your local machine.
1515
3. Install the project dependencies using npm - `npm install`.
16+
4. Install Playwright browsers if you plan to run the E2E tests: `npx playwright install --with-deps`.
1617

1718
This will install all the necessary packages and dependencies required to run the project.
1819

@@ -28,6 +29,7 @@ Open [http://localhost:5173](http://localhost:5173) with your browser to see the
2829

2930
- `npm run start`: Starts the development server.
3031
- `npm run build`: Builds the app for production.
32+
- `npm run test:e2e`: Runs all End-to-End tests with Playwright.
3133

3234
## Configuration
3335

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Tests for code editing functionality and AST tool interaction.
3+
*/
4+
import test, { expect } from "@playwright/test";
5+
6+
/**
7+
* This test verifies that:
8+
* - Users can edit code in the editor
9+
* - The AST updates in response to code changes
10+
* - ESQuery selectors correctly highlight matching code and AST nodes
11+
* - AST node expansion functionality works properly
12+
*/
13+
test(`should change code, then highlight code and AST nodes matching ESQuery selector`, async ({
14+
page,
15+
}) => {
16+
await page.goto("/");
17+
18+
// focus code editor textbox
19+
await page
20+
.getByRole("region", { name: "Code Editor Panel" })
21+
.getByRole("textbox")
22+
.nth(1)
23+
.click();
24+
25+
// delete the default code
26+
await page.keyboard.press("ControlOrMeta+KeyA");
27+
await page.keyboard.press("Backspace");
28+
29+
// add new code
30+
await page.keyboard.type("console.log('Hello, World!');");
31+
32+
// add an ESQuery selector
33+
await page.getByRole("textbox", { name: "ESQuery Selector" }).click();
34+
await page.keyboard.type("CallExpression");
35+
36+
// wait for the debounced update of the AST to happen
37+
await expect(
38+
page
39+
.getByRole("listitem")
40+
.filter({ hasText: "end" })
41+
.filter({ hasText: "29" }),
42+
).toBeVisible();
43+
44+
// expand AST nodes for ExpressionStatement and CallExpression
45+
await page
46+
.getByRole("region", { name: "Program" })
47+
.getByRole("listitem")
48+
.filter({ hasText: "bodyArray[1 element]" })
49+
.getByLabel("Toggle Property")
50+
.click();
51+
await page.getByRole("button", { name: "ExpressionStatement" }).click();
52+
await page
53+
.getByRole("region", { name: "Program" })
54+
.getByRole("listitem")
55+
.filter({ hasText: "expressionCallExpression{type" })
56+
.getByLabel("Toggle Property")
57+
.click();
58+
});

e2e-tests/light-dark-theme.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Tests for theme switching functionality.
3+
*/
4+
5+
import { test } from "@playwright/test";
6+
7+
/**
8+
* This test verifies that:
9+
* - The application shows light theme by default
10+
* - Users can toggle between light and dark themes
11+
* - Theme changes are visually reflected in the UI
12+
*/
13+
test("should show light theme by default and switch to dark theme", async ({
14+
page,
15+
}) => {
16+
await page.goto("/");
17+
18+
await page.getByRole("button", { name: "Toggle theme" }).click();
19+
await page.getByRole("menuitem", { name: "Dark" }).click();
20+
});

e2e-tests/options.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Tests for language selection and options panel functionality.
3+
*/
4+
import { test } from "@playwright/test";
5+
6+
/**
7+
* This test verifies that:
8+
* - Users can open the language options popover
9+
* - Users can switch between supported languages (JavaScript, JSON, Markdown, CSS, HTML)
10+
* - For each language the entire page is correctly rendered
11+
*/
12+
test("should switch language and show options for each", async ({ page }) => {
13+
await page.goto("/");
14+
15+
await page.getByRole("button", { name: "Language Options" }).click();
16+
17+
await page.getByRole("combobox", { name: "Language" }).click();
18+
await page.getByRole("option", { name: "JSON JSON" }).click();
19+
20+
await page.getByRole("combobox", { name: "Language" }).click();
21+
await page.getByRole("option", { name: "Markdown Markdown" }).click();
22+
23+
await page.getByRole("combobox", { name: "Language" }).click();
24+
await page.getByRole("option", { name: "CSS CSS" }).click();
25+
26+
await page.getByRole("combobox", { name: "Language" }).click();
27+
await page.getByRole("option", { name: "HTML HTML" }).click();
28+
});

e2e-tests/tools.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Tests for the Code Analysis Tools Panel.
3+
*/
4+
import { test } from "@playwright/test";
5+
6+
/**
7+
* This test verifies that:
8+
* - Users can switch between different code analysis tools (AST, Scope, Code Path)
9+
* - Each tool displays correctly
10+
* - Tool-specific interactions work as expected (e.g. scope selection)
11+
*/
12+
test("should switch to each tool and show it", async ({ page }) => {
13+
await page.goto("/");
14+
15+
await page.getByRole("button", { name: "Scope" }).click();
16+
await page.getByRole("button", { name: "global" }).click();
17+
// move mouse away to avoid accordion hover state
18+
await page.mouse.move(0, 0);
19+
20+
await page.getByRole("button", { name: "Code Path" }).click();
21+
});

package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"lint": "eslint",
1515
"lint:fix": "eslint --fix",
1616
"fmt": "prettier --write .",
17-
"fmt:check": "prettier --check ."
17+
"fmt:check": "prettier --check .",
18+
"test:e2e": "playwright test"
1819
},
1920
"lint-staged": {
2021
"**/*.{js,jsx,ts,tsx}": [
@@ -72,6 +73,7 @@
7273
},
7374
"devDependencies": {
7475
"@eslint/core": "^0.17.0",
76+
"@playwright/test": "^1.57.0",
7577
"@types/eslint-scope": "^8.3.2",
7678
"@types/espree": "^10.1.0",
7779
"@types/esquery": "^1.5.4",

playwright.config.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import os from "node:os";
2+
3+
import { defineConfig, devices } from "@playwright/test";
4+
5+
const countOfCpus = os.cpus().length;
6+
const workers = countOfCpus
7+
? // utilize all logical processors up to a max of 4 to limit RAM usage
8+
Math.min(countOfCpus, 4)
9+
: undefined;
10+
11+
const isInCi = process.env.CI === "true";
12+
13+
export default defineConfig({
14+
testDir: "./e2e-tests",
15+
fullyParallel: true,
16+
// fail a Playwright run in CI if some test.only is in the source code
17+
forbidOnly: isInCi,
18+
retries: isInCi ? 1 : 0,
19+
workers,
20+
reporter: isInCi ? [["html"], ["github"]] : "html",
21+
22+
use: {
23+
baseURL: `http://localhost:5173`,
24+
25+
// ensure consistent timezone and locale
26+
timezoneId: "America/Los_Angeles",
27+
locale: "en-US",
28+
29+
// always capture trace and video (seems to not have significant performance impact)
30+
trace: "on",
31+
video: "on",
32+
},
33+
34+
projects: [
35+
{
36+
name: "chromium",
37+
use: {
38+
...devices["Desktop Chrome"],
39+
// opt into "New Headless" chromium (https://playwright.dev/docs/browsers#chromium-new-headless-mode, https://developer.chrome.com/docs/chromium/headless)
40+
channel: "chromium",
41+
},
42+
},
43+
{
44+
name: "firefox",
45+
use: { ...devices["Desktop Firefox"] },
46+
},
47+
{
48+
name: "webkit",
49+
use: { ...devices["Desktop Safari"] },
50+
},
51+
],
52+
53+
webServer: [
54+
{
55+
command: "npm run start",
56+
url: "http://localhost:5173",
57+
reuseExistingServer: !isInCi,
58+
},
59+
],
60+
});

0 commit comments

Comments
 (0)