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
10 changes: 10 additions & 0 deletions .changeset/calm-forks-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@pvmdbg/cli": patch
"@pvmdbg/content": patch
"@pvmdbg/orchestrator": patch
"@pvmdbg/runtime-worker": patch
"@pvmdbg/trace": patch
"@pvmdbg/types": patch
---

Initial
16 changes: 16 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh
# Pre-push hook: runs the same checks as CI to prevent failures.
# Install: git config core.hooksPath .githooks

set -e

echo "==> Building all packages..."
npm run build

echo "==> Running lint..."
npx biome check . --error-on-warnings

echo "==> Running unit tests..."
npm test

echo "==> All checks passed."
43 changes: 29 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,15 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Build packages
run: |
for pkg in packages/*/; do
npm run build -w "$pkg"
done
- name: Build all packages and web app
run: npm run build

- name: Lint
run: npx biome check .

- name: Unit tests
run: npm test

- name: Build web app
run: npm run build -w apps/web

- name: Install Playwright Chromium
run: npx playwright install chromium --with-deps

- name: E2E tests
run: npm run test:e2e -w apps/web

- name: Check changeset
if: ${{ !startsWith(github.head_ref, 'changeset-release/') }}
run: |
Expand All @@ -51,3 +39,30 @@ jobs:
echo "::error::No changeset file found. Run 'npx changeset' to add one."
exit 1
fi

e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: ci

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- name: Install dependencies
run: npm ci

- name: Build all packages and web app
run: npm run build

- name: Install Playwright Chromium
run: npx playwright install chromium --with-deps

- name: E2E tests
run: npm run test:e2e -w apps/web
4 changes: 3 additions & 1 deletion .github/workflows/publish-next.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Build web app
- name: Build web app for GitHub Pages
run: npm run build -w apps/web
env:
VITE_BASE_PATH: /pvm-debugger/

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
Expand Down
6 changes: 3 additions & 3 deletions apps/web/e2e/integration-smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { expect, test } from "@playwright/test";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -116,7 +116,7 @@ test.describe("Sprint 36 — Integration Smoke Test", () => {
const gasText = await gasValue.textContent();
expect(gasText).toBeTruthy();
// Gas should be parseable as a number (may have thousands separators)
const gasNum = Number(gasText!.replace(/,/g, ""));
const gasNum = Number(gasText?.replace(/,/g, ""));
expect(gasNum).toBeGreaterThan(0);
});

Expand Down
2 changes: 1 addition & 1 deletion apps/web/e2e/sprint-03-instructions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ test.describe("Sprint 03 — Flat Instruction List", () => {
let foundOmega = false;
for (let i = 0; i < count; i++) {
const text = await argsElements.nth(i).textContent();
if (text && text.includes("ω")) {
if (text?.includes("ω")) {
foundOmega = true;
break;
}
Expand Down
10 changes: 5 additions & 5 deletions apps/web/e2e/sprint-07-layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ test.describe("Sprint 07 — 3-Column Debugger Layout", () => {
expect(mBox).toBeTruthy();

// Instructions is to the left of Registers
expect(iBox!.x + iBox!.width).toBeLessThanOrEqual(rBox!.x + 2);
expect(iBox?.x + iBox?.width).toBeLessThanOrEqual(rBox?.x + 2);
// Registers is to the left of Memory
expect(rBox!.x + rBox!.width).toBeLessThanOrEqual(mBox!.x + 2);
expect(rBox?.x + rBox?.width).toBeLessThanOrEqual(mBox?.x + 2);
});

test("panel headers align at the same height", async ({ page }) => {
Expand All @@ -54,8 +54,8 @@ test.describe("Sprint 07 — 3-Column Debugger Layout", () => {
expect(mBox).toBeTruthy();

// All panels start at the same Y coordinate (header alignment)
expect(Math.abs(iBox!.y - rBox!.y)).toBeLessThan(2);
expect(Math.abs(rBox!.y - mBox!.y)).toBeLessThan(2);
expect(Math.abs(iBox?.y - rBox?.y)).toBeLessThan(2);
expect(Math.abs(rBox?.y - mBox?.y)).toBeLessThan(2);
});

test("toolbar row is visible above the panels", async ({ page }) => {
Expand All @@ -74,7 +74,7 @@ test.describe("Sprint 07 — 3-Column Debugger Layout", () => {
expect(pBox).toBeTruthy();

// Toolbar is above the panel area
expect(tBox!.y + tBox!.height).toBeLessThanOrEqual(pBox!.y + 2);
expect(tBox?.y + tBox?.height).toBeLessThanOrEqual(pBox?.y + 2);
});

test("each panel scrolls independently", async ({ page }) => {
Expand Down
6 changes: 3 additions & 3 deletions apps/web/e2e/sprint-10-file-upload.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { expect, test } from "@playwright/test";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -122,7 +122,7 @@ test.describe("Sprint 10 — File Upload Source", () => {
expect(leftBox).toBeTruthy();
expect(rightBox).toBeTruthy();
// In stacked layout, the left column top should be above right column top
expect(leftBox!.y).toBeLessThan(rightBox!.y);
expect(leftBox?.y).toBeLessThan(rightBox?.y);
});

test("example cards still work in two-column layout", async ({ page }) => {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/e2e/sprint-11-url-and-hex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ test.describe("Sprint 11 — URL + Manual Hex Sources", () => {

// Upload a file — should clear hex result
const fileInput = page.getByTestId("file-upload-input");
const path = await import("path");
const url = await import("url");
const path = await import("node:path");
const url = await import("node:url");
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const fixturesDir = path.resolve(__dirname, "../../../fixtures");
Expand Down
4 changes: 2 additions & 2 deletions apps/web/e2e/sprint-12-detection-summary.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from "node:path";
import * as url from "node:url";
import { expect, test } from "@playwright/test";
import * as path from "path";
import * as url from "url";

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/e2e/sprint-13-spi-config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from "node:path";
import * as url from "node:url";
import { expect, test } from "@playwright/test";
import * as path from "path";
import * as url from "url";

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down
20 changes: 10 additions & 10 deletions apps/web/e2e/sprint-14-drawer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ test.describe("Sprint 14 — Bottom Drawer Shell", () => {
// Drawer should be taller than just the tab bar
const box = await drawer.boundingBox();
expect(box).toBeTruthy();
expect(box!.height).toBeGreaterThan(40);
expect(box?.height).toBeGreaterThan(40);
});

test("clicking the active tab collapses the drawer", async ({ page }) => {
Expand Down Expand Up @@ -101,15 +101,15 @@ test.describe("Sprint 14 — Bottom Drawer Shell", () => {
const drawer = page.getByTestId("bottom-drawer");
const initialBox = await drawer.boundingBox();
expect(initialBox).toBeTruthy();
const initialHeight = initialBox!.height;
const initialHeight = initialBox?.height;

// Drag the handle upward to expand
const handle = page.getByTestId("drawer-drag-handle");
const handleBox = await handle.boundingBox();
expect(handleBox).toBeTruthy();

const startX = handleBox!.x + handleBox!.width / 2;
const startY = handleBox!.y + handleBox!.height / 2;
const startX = handleBox?.x + handleBox?.width / 2;
const startY = handleBox?.y + handleBox?.height / 2;

await page.mouse.move(startX, startY);
await page.mouse.down();
Expand All @@ -119,7 +119,7 @@ test.describe("Sprint 14 — Bottom Drawer Shell", () => {
const afterBox = await drawer.boundingBox();
expect(afterBox).toBeTruthy();
// Drawer should be taller after dragging up
expect(afterBox!.height).toBeGreaterThan(initialHeight + 50);
expect(afterBox?.height).toBeGreaterThan(initialHeight + 50);
});

test("drawer height clamps to 60% of viewport maximum", async ({ page }) => {
Expand All @@ -133,8 +133,8 @@ test.describe("Sprint 14 — Bottom Drawer Shell", () => {
const handleBox = await handle.boundingBox();
expect(handleBox).toBeTruthy();

const startX = handleBox!.x + handleBox!.width / 2;
const startY = handleBox!.y + handleBox!.height / 2;
const startX = handleBox?.x + handleBox?.width / 2;
const startY = handleBox?.y + handleBox?.height / 2;

// Drag far upward — well beyond 60% of viewport
await page.mouse.move(startX, startY);
Expand All @@ -148,7 +148,7 @@ test.describe("Sprint 14 — Bottom Drawer Shell", () => {
const drawer = page.getByTestId("bottom-drawer");
const box = await drawer.boundingBox();
expect(box).toBeTruthy();
expect(box!.height).toBeLessThanOrEqual(maxAllowed);
expect(box?.height).toBeLessThanOrEqual(maxAllowed);
});

test("drawer is positioned below the panel area", async ({ page }) => {
Expand All @@ -164,8 +164,8 @@ test.describe("Sprint 14 — Bottom Drawer Shell", () => {
expect(drawerBox).toBeTruthy();

// Drawer top should be at or below panels bottom
expect(drawerBox!.y).toBeGreaterThanOrEqual(
panelsBox!.y + panelsBox!.height - 2,
expect(drawerBox?.y).toBeGreaterThanOrEqual(
panelsBox?.y + panelsBox?.height - 2,
);
});
});
16 changes: 12 additions & 4 deletions apps/web/e2e/sprint-19-host-call-tab.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ test.describe("Sprint 19 — Host Call Drawer Tab", () => {
const headerText = await page
.getByTestId("host-call-header")
.textContent();
if (headerText && headerText.includes("log")) {
if (headerText?.includes("log")) {
foundLog = true;
break;
}
Expand Down Expand Up @@ -173,15 +173,23 @@ test.describe("Sprint 19 — Host Call Drawer Tab", () => {
.getByTestId("storage-host-call")
.isVisible()
.catch(() => false);
const fetchVisible = await page
.getByTestId("fetch-host-call")
.isVisible()
.catch(() => false);
const genericVisible = await page
.getByTestId("generic-host-call")
.isVisible()
.catch(() => false);

// Exactly one should be visible
expect(gasVisible || logVisible || storageVisible || genericVisible).toBe(
true,
);
expect(
gasVisible ||
logVisible ||
storageVisible ||
fetchVisible ||
genericVisible,
).toBe(true);
});

test("no resume button is present in the tab", async ({ page }) => {
Expand Down
Loading
Loading