Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
47c8ff1
ref: centralize Vitest tests under root tests
tukuyomil032 Sep 2, 2026
a66807a
test: move Playwright E2E into dedicated tests directory
tukuyomil032 Sep 2, 2026
c339ff5
fix: preserve structured plugin failure codes
tukuyomil032 Sep 2, 2026
59950c2
test: cover application feature flows with Playwright
tukuyomil032 Sep 2, 2026
bb77485
test: cover plugin install feedback behavior
tukuyomil032 Sep 2, 2026
aee5123
test: verify shared feedback presentation
tukuyomil032 Sep 2, 2026
9526cc1
chore: stabilize Playwright CI execution
tukuyomil032 Sep 2, 2026
e9d8f0a
chore: consolidate Playwright workflow commands
tukuyomil032 Sep 2, 2026
dca7fc8
fix: use supported cargo-audit invocation
tukuyomil032 Sep 2, 2026
64c043d
fix: make managed Java path test portable
tukuyomil032 Sep 2, 2026
d566130
fix: make managed path resolution CodeQL-safe
tukuyomil032 Sep 2, 2026
b9e3719
chore: update Rust audit dependencies
tukuyomil032 Sep 2, 2026
1c32efe
fix: make managed Java path tests portable
tukuyomil032 Sep 2, 2026
85c0035
fix: harden plugin download path data flow
tukuyomil032 Sep 2, 2026
2fac4a7
Merge branch 'fix/feedback-policy' into fix/security-audit-hardening
tukuyomil032 Sep 2, 2026
7fc41f2
Merge branch 'fix/feedback-policy' into fix/security-audit-hardening
tukuyomil032 Sep 2, 2026
5ab399d
Merge Layer 4 feedback policy
tukuyomil032 Sep 2, 2026
04021e6
fix: make picker and managed copy flows CodeQL-safe
tukuyomil032 Sep 2, 2026
4d5a5b1
Merge corrected lower-layer hardening
tukuyomil032 Sep 2, 2026
fa0ae18
fix: make recursive file flows CodeQL-safe
tukuyomil032 Sep 2, 2026
1ee6982
Merge recursive CodeQL remediation
tukuyomil032 Sep 2, 2026
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
22 changes: 4 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,6 @@ jobs:
pnpm check
git diff --exit-code

playwright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
- name: Run Playwright tests
run: pnpm test:playwright

build-check:
runs-on: ${{ matrix.platform }}
strategy:
Expand All @@ -62,8 +46,10 @@ jobs:
- name: Install just
uses: taiki-e/install-action@ae666e311a8d98d0043e81161903fc9f9beb8689 # just
- run: pnpm install --frozen-lockfile
- name: Run Vitest
run: pnpm test
- name: Run Vitest and test typecheck
run: |
pnpm test
pnpm typecheck:tests
- name: Run cargo test
run: cargo test
working-directory: src-tauri
Expand Down
16 changes: 9 additions & 7 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
paths:
- "src/**"
- "src-tauri/**"
- "playwright/**"
- "tests/**"
- "playwright.config.ts"
- "package.json"
- "pnpm-lock.yaml"
Expand All @@ -25,9 +25,9 @@ jobs:
timeout-minutes: 15

steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '22'
cache: 'pnpm'
Expand All @@ -36,11 +36,13 @@ jobs:
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
- name: Run Playwright E2E tests
run: pnpm test:playwright
run: pnpm e2e
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report
path: playwright-report/
path: |
playwright-report/
test-results/
retention-days: 7
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dev:plain": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "biome check --diagnostic-level=error src",
"check": "biome check --diagnostic-level=error src tests",
"check:fix": "biome check --write --diagnostic-level=error src",
"lint": "biome lint --diagnostic-level=error src",
"format": "biome format --write src",
Expand All @@ -26,10 +26,11 @@
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
"test:typecheck": "tsc -p tsconfig.tests.json --noEmit",
"test:all": "pnpm check && pnpm test && cd src-tauri && cargo test",
"test:playwright": "playwright test",
"test:playwright:ui": "playwright test --ui",
"test:playwright:screenshots": "playwright test capture-theme-screenshots"
"e2e": "playwright test",
"e2e:ui": "playwright test --ui"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
Expand Down Expand Up @@ -87,6 +88,7 @@
"@tauri-apps/cli": "^2.11.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/node": "^24.10.1",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
Expand Down
11 changes: 7 additions & 4 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { defineConfig, devices } from '@playwright/test';
import process from 'node:process';

export default defineConfig({
testDir: './playwright/tests',
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI ? 'github' : 'list',

use: {
baseURL: 'http://localhost:5173',
baseURL: 'http://127.0.0.1:5173',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
launchOptions: {
args: ['--force-prefers-reduced-motion'],
},
},

projects: [
Expand All @@ -23,8 +26,8 @@ export default defineConfig({
],

webServer: {
command: 'VITE_PLAYWRIGHT=true pnpm dev:plain',
url: 'http://localhost:5173',
command: 'HOST=127.0.0.1 VITE_PLAYWRIGHT=true pnpm dev:plain',
url: 'http://127.0.0.1:5173',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
stdout: 'pipe',
Expand Down
44 changes: 0 additions & 44 deletions playwright/tests/navigation.spec.ts

This file was deleted.

3 changes: 3 additions & 0 deletions pnpm-lock.yaml

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

21 changes: 21 additions & 0 deletions src/lib/plugin-install-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,17 @@ function decodeErrorText(value: unknown, depth = 0): string | undefined {
return undefined;
}

if (Object.prototype.hasOwnProperty.call(FAILURE_DETAILS, text)) {
return text as PluginInstallFailureCode;
}

try {
const decoded = JSON.parse(text) as unknown;
if (!(typeof decoded === 'string' && decoded === text)) {
const decodedCode = readKnownCode(decoded, depth + 1);
if (decodedCode) {
return decodedCode;
}
const decodedText = decodeErrorText(decoded, depth + 1);
if (decodedText) {
return decodedText;
Expand Down Expand Up @@ -128,6 +136,19 @@ function readKnownCode(value: unknown, depth = 0): PluginInstallFailureCode | un
return value as PluginInstallFailureCode;
}

if (typeof value === 'string') {
try {
const parsed = JSON.parse(value) as unknown;
const parsedCode = readKnownCode(parsed, depth + 1);
if (parsedCode) return parsedCode;
} catch {
const embeddedCode = value.match(/"code"\s*:\s*"([^"]+)"/i)?.[1];
if (embeddedCode && Object.prototype.hasOwnProperty.call(FAILURE_DETAILS, embeddedCode)) {
return embeddedCode as PluginInstallFailureCode;
}
}
}

if (value !== null && typeof value === 'object') {
const code = readProperty(value, 'code');
if (typeof code === 'string' && Object.prototype.hasOwnProperty.call(FAILURE_DETAILS, code)) {
Expand Down
12 changes: 10 additions & 2 deletions src/renderer/components/AddServerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,11 @@ const AddServerModal: FC<AddServerModalProps> = ({ open: isOpen, onClose, onAdd,
<div className="add-server-modal__row">
<div className="add-server-modal__field-group">
<label className="add-server-modal__label">{t('addServer.software.label')}</label>
<select {...register('software')} className="add-server-modal__field">
<select
{...register('software')}
data-testid="server-software-select"
className="add-server-modal__field"
>
<optgroup label={t('addServer.software.groups.standard')}>
<option value="Vanilla">{t('addServer.software.options.vanilla')}</option>
<option value="Paper">{t('addServer.software.options.paper')}</option>
Expand All @@ -223,7 +227,11 @@ const AddServerModal: FC<AddServerModalProps> = ({ open: isOpen, onClose, onAdd,

<div className="add-server-modal__field-group">
<label className="add-server-modal__label">{t('addServer.version.label')}</label>
<select {...register('version')} className="add-server-modal__field">
<select
{...register('version')}
data-testid="server-version-select"
className="add-server-modal__field"
>
{VERSION_OPTIONS.map((v) => (
<option key={v} value={v}>
{v}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/AppFeedbackProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ function FeedbackDialogContent({
}) {
return (
<Dialog.Content
data-testid="feedback-dialog"
className="mc-modal-panel fixed left-1/2 top-1/2 z-[1001] max-h-[calc(100vh-2rem)] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto"
onEscapeKeyDown={dialog.dismissible === false ? (event) => event.preventDefault() : undefined}
onPointerDownOutside={
Expand Down
7 changes: 6 additions & 1 deletion src/renderer/components/BackupsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -751,11 +751,12 @@ export default function BackupsView({ server }: Props) {
};

return (
<div className="backups-view">
<div className="backups-view" data-testid="backups-view">
<div className="backups-view__header">
<h3>{t('backups.title')}</h3>
<button
className="btn-primary disabled:opacity-70"
data-testid="backups-create-button"
onClick={openCreateModal}
disabled={processing}
>
Expand All @@ -779,6 +780,7 @@ export default function BackupsView({ server }: Props) {
key={backup.name}
ref={backupVirtualizer.measureElement}
data-index={virtualRow.index}
data-testid={`backup-row-${backup.name}`}
className="backups-view__item-row"
style={{ position: 'absolute', top: virtualRow.start, left: 0, width: '100%' }}
>
Expand Down Expand Up @@ -901,6 +903,7 @@ export default function BackupsView({ server }: Props) {
<label className="backups-view__form-label">{t('backups.modal.fileName')}</label>
<input
className="input-field"
data-testid="backup-name-input"
placeholder={defaultName()}
value={customName}
onChange={(e) => setCustomName(e.target.value)}
Expand Down Expand Up @@ -949,6 +952,7 @@ export default function BackupsView({ server }: Props) {
<div className="flex gap-2">
<button
className="btn-secondary text-sm"
data-testid="backups-open-selector-button"
onClick={() => void openSelectorWindow()}
>
{t('backups.modal.openSelector')}
Expand Down Expand Up @@ -996,6 +1000,7 @@ export default function BackupsView({ server }: Props) {
</button>
<button
className="btn-primary"
data-testid="backups-create-submit"
onClick={handleCreateBackup}
disabled={processing || selectedPaths.size === 0}
>
Expand Down
13 changes: 11 additions & 2 deletions src/renderer/components/ConsoleView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ const ConsoleView: FC<ConsoleViewProps> = ({ server, ngrokUrl }) => {
};

return (
<div className="console-view">
<div className="console-view" data-testid="console-view">
<section className="console-view__status-strip surface-card">
<div className="console-view__status-col console-view__status-col--with-divider">
<div className="console-view__status-label">{t('console.status.address')}</div>
Expand Down Expand Up @@ -712,6 +712,7 @@ const ConsoleView: FC<ConsoleViewProps> = ({ server, ngrokUrl }) => {
}}
placeholder={t('console.search.placeholder')}
className="console-view__search-input"
data-testid="console-search-input"
/>

<div className="console-view__search-count">
Expand Down Expand Up @@ -766,6 +767,7 @@ const ConsoleView: FC<ConsoleViewProps> = ({ server, ngrokUrl }) => {
</span>
<button
type="button"
data-testid="console-search-open-button"
className="console-view__search-open-btn control-chip"
onClick={openSearch}
>
Expand Down Expand Up @@ -946,6 +948,7 @@ const ConsoleView: FC<ConsoleViewProps> = ({ server, ngrokUrl }) => {
<button
type="button"
className="console-view__save-button control-chip"
data-testid="console-save-button"
onClick={() => void handleExportLogs()}
>
{t('console.actions.saveLogs')}
Expand Down Expand Up @@ -991,8 +994,14 @@ const ConsoleView: FC<ConsoleViewProps> = ({ server, ngrokUrl }) => {
onKeyDown={handleKeyDown}
placeholder={t('console.command.placeholder')}
className="console-view__command-input"
data-testid="console-command-input"
/>
<button type="button" onClick={handleSend} className="console-view__send-button">
<button
type="button"
onClick={handleSend}
className="console-view__send-button"
data-testid="console-send-button"
>
{t('console.actions.send')}
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/DashboardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ export default function DashboardView({ server }: Props) {
}, [server.id, server.status, supportsTpsPolling]);

return (
<div className="dashboard-view">
<div className="dashboard-view" data-testid="dashboard-view">
<header className="dashboard-view__header surface-card">
<div className="dashboard-view__header-main">
<h2 className="dashboard-view__title">{t('dashboard.title', { name: server.name })}</h2>
Expand Down
Loading
Loading