Skip to content
Draft
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ CLAUDE.md

# JS test dependencies
node_modules/
js-tests/playwright-report/
js-tests/test-results/

.vscode
.vault
Expand Down
28 changes: 27 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,28 @@ benchmark-real:

## test-js: Run all JavaScript tests (tree view + config dialog) and check ES5 compatibility.
.PHONY: test-js
test-js: tree-view-fixture config-dialog-fixture
test-js: tree-view-fixture config-dialog-fixture panel-fixtures
@echo "==> Running JS tests..."
@cd js-tests && npm install --ignore-scripts && npm test
@echo "==> Linting JS for ES5 compatibility..."
@cd js-tests && npm run lint:es5

## test-js-browser-setup: Install Playwright browser runtime for optional JS browser tests.
.PHONY: test-js-browser-setup
test-js-browser-setup:
@echo "==> Installing Playwright browser runtime..."
@cd js-tests && npm install --ignore-scripts && npm run playwright:install

## test-js-browser: Run optional Playwright browser tests for tree view runtime.
.PHONY: test-js-browser
test-js-browser: tree-view-fixture config-dialog-fixture panel-fixtures
@echo "==> Running JS browser tests (Playwright)..."
@cd js-tests && npm install --ignore-scripts && npm run test:playwright

## test-js-all: Run all JS tests including optional Playwright browser tests.
.PHONY: test-js-all
test-js-all: test-js test-js-browser

.PHONY: race-test
race-test:
@echo "==> Running integration tests with race-detector..."
Expand Down Expand Up @@ -225,6 +241,16 @@ config-dialog-fixture:
@go run scripts/config-dialog/main.go --dummy-data -no-panel > js-tests/fixtures/config-page.html
@echo " Written to js-tests/fixtures/config-page.html"

## panel-fixtures: Regenerate code/oss/iac/secrets/scan-summary HTML fixtures used by JS tests.
.PHONY: panel-fixtures
panel-fixtures:
@echo "==> Generating panel HTML fixtures..."
@mkdir -p js-tests/fixtures
@for p in code-suggestion oss-suggestion iac-suggestion secrets-suggestion scan-summary; do \
echo " --> $$p" ; \
go run ./scripts/panel-fixtures --panel $$p --output-file js-tests/fixtures/$$p.html ; \
done

## generate: Regenerate generated files (e.g. mocks).
.PHONY: generate
generate:
Expand Down
92 changes: 92 additions & 0 deletions js-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# JS tests

Two suites live here:

- **Node tests** (`*.test.mjs`) — fast unit tests of webview helpers (tree runtime, dirty tracker, tabs, bridge, form/auth handlers). Run via `node --test` with jsdom.
- **Playwright tests** (`*.playwright.spec.mjs`) — browser tests that load the real `domain/ide/treeview/template/tree.js` runtime in headless Chromium and assert on rendered DOM, computed CSS, and the `__ideExecuteCommand__` bridge calls each IDE would receive.

The Playwright suite is **opt-in** because it requires a Chromium download (~150 MB) and is slower than `node --test`.

## One-time setup

```sh
make test-js-browser-setup
```

This runs `npm install` in `js-tests/` and downloads the Chromium build Playwright drives.

## Running locally

| Command | What it runs |
| ----------------------------- | ---------------------------------------------------------------- |
| `make test-js` | Node tests + ES5 lint. Default for JS work. |
| `make test-js-browser` | Playwright tests only (requires `test-js-browser-setup` first). |
| `make test-js-all` | Node tests + Playwright tests. |

Direct npm equivalents (from `js-tests/`):

```sh
npm test # node --test
npm run test:playwright # playwright test (headless)
npm run test:playwright:headed # playwright test --headed (watch the browser)
```

## Visual regression baselines

`panel-screenshots.playwright.spec.mjs` renders each panel HTML fixture (`fixtures/tree-view.html`, `fixtures/config-page.html`) under each IDE's body bg/fg colors and compares against committed PNG baselines under `screenshots/`.

Baselines are **per-platform** (`<name>-{darwin,linux}.png`) because Chromium's font rendering differs across OSes. Tests use `threshold: 0.2` and `maxDiffPixelRatio: 0.02` to absorb minor antialiasing noise.

After an intentional UI change (panel HTML, CSS, or fixture content), regenerate the baselines for your platform and commit them:

```sh
cd js-tests
npx playwright test panel-screenshots.playwright.spec.mjs --update-snapshots
git add screenshots/
```

CI runs on Linux, so Linux baselines (`*-linux.png`) must also be committed when the visual changes. Generate them by running `--update-snapshots` in a Linux environment (CI container, Docker, etc.) and committing the result.

When a screenshot diff fails unintentionally, the HTML report (`npx playwright show-report`) shows the expected, actual, and diff PNGs side-by-side under "Attachments".

## Inspecting failures

When a Playwright test fails, the config retains a trace, screenshot, and video for that run:

```sh
cd js-tests
npx playwright show-report # opens playwright-report/ (HTML)
npx playwright show-trace test-results/<failing-test>/trace.zip
```

Use headed mode to step through interactively:

```sh
cd js-tests
npx playwright test --headed --debug
```

Both `playwright-report/` and `test-results/` are gitignored.

## CI/CD

Today, `.github/workflows/build.yaml` runs `make clean test`, which depends on `test-js` (Node tests + ES5 lint). The Playwright suite is **not** in the default CI path.

To add it, run `test-js-browser` after the existing test step on Linux. Chromium download is cached by `actions/setup-node` between runs:

```yaml
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: js-tests/package-lock.json

- name: Install Playwright browser
run: make test-js-browser-setup

- name: Run Playwright browser tests
run: make test-js-browser
```

Recommended placement: a new `js-browser-tests` job in `build.yaml` that runs in parallel with `integration-tests` (Linux-only — Chromium launch on macOS/Windows runners adds time without meaningful additional coverage for these tests).
70 changes: 67 additions & 3 deletions js-tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions js-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
"type": "module",
"scripts": {
"test": "node --test --experimental-test-coverage",
"test:playwright": "playwright test",
"test:playwright:headed": "playwright test --headed",
"playwright:install": "playwright install chromium",
"lint:es5": "eslint .."
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"eslint": "^10.1.0",
"jsdom": "^26.1.0"
}
Expand Down
45 changes: 45 additions & 0 deletions js-tests/panel-screenshots.playwright.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { test, expect } from "@playwright/test";
import { readFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const ideVariants = [
{ name: "vscode", background: "rgb(30, 30, 30)", foreground: "rgb(204, 204, 204)" },
{ name: "intellij", background: "rgb(43, 43, 43)", foreground: "rgb(187, 187, 187)" },
{ name: "visualStudio", background: "rgb(37, 37, 38)", foreground: "rgb(241, 241, 241)" },
{ name: "eclipse", background: "rgb(27, 30, 35)", foreground: "rgb(216, 221, 229)" },
];

const panels = [
{ name: "tree-view", fixture: "tree-view.html" },
{ name: "config-page", fixture: "config-page.html" },
{ name: "code-suggestion", fixture: "code-suggestion.html" },
{ name: "oss-suggestion", fixture: "oss-suggestion.html" },
{ name: "iac-suggestion", fixture: "iac-suggestion.html" },
{ name: "secrets-suggestion", fixture: "secrets-suggestion.html" },
{ name: "scan-summary", fixture: "scan-summary.html" },
];

for (const panel of panels) {
for (const ide of ideVariants) {
test(`screenshot: ${panel.name} × ${ide.name}`, async ({ page }) => {
const rawHtml = await readFile(join(__dirname, "fixtures", panel.fixture), "utf8");
// Strip CSP meta tags — fixtures contain webview CSPs that block test-injected styles.
const html = rawHtml.replace(/<meta[^>]*http-equiv=["']Content-Security-Policy["'][^>]*>/gi, "");
await page.setViewportSize({ width: 1280, height: 900 });
await page.setContent(html, { waitUntil: "domcontentloaded" });
await page.addStyleTag({
content: `body { background-color: ${ide.background} !important; color: ${ide.foreground} !important; }`,
});
await expect(page).toHaveScreenshot(`${panel.name}-${ide.name}.png`, {
fullPage: true,
threshold: 0.2,
maxDiffPixelRatio: 0.02,
animations: "disabled",
});
});
}
}
26 changes: 26 additions & 0 deletions js-tests/playwright.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: ".",
testMatch: ["*.playwright.spec.mjs"],
timeout: 30_000,
expect: {
timeout: 5_000,
},
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: [["list"], ["html", { open: "never", outputFolder: "playwright-report" }]],
snapshotPathTemplate: "screenshots/{arg}-{platform}{ext}",
use: {
...devices["Desktop Chrome"],
trace: process.env.PW_TRACE || "retain-on-failure",
screenshot: process.env.PW_SCREENSHOT || "on",
video: process.env.PW_VIDEO || "retain-on-failure",
},
projects: [
{
name: "chromium",
},
],
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading