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
17 changes: 17 additions & 0 deletions .github/workflows/qa-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,31 @@ jobs:
run: npx playwright install chromium

- name: Capture UI screenshot
id: capture_ui_screenshot
run: npm run qa:screenshot
env:
UI_SCREENSHOT_NAME: ui-${{ runner.os }}.png

- name: Run Electron E2E (Playwright)
if: runner.os == 'Linux'
run: xvfb-run -a npm run e2e:playwright:ci

- name: Upload UI screenshot
if: always() && steps.capture_ui_screenshot.outcome == 'success'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: ui-screenshot-${{ runner.os }}
path: dist/qa/screenshots/*.png
if-no-files-found: error
retention-days: 14

- name: Upload Playwright E2E artifacts
if: runner.os == 'Linux' && always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: playwright-e2e-linux
path: |
dist/qa/playwright-report
dist/qa/playwright-results
if-no-files-found: warn
retention-days: 14
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
'^yaml$': '<rootDir>/tests/mocks/yaml-mock.ts',
},
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
testPathIgnorePatterns: ['/node_modules/'],
testPathIgnorePatterns: ['/node_modules/', '/tests/e2e/'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"qa": "node scripts/index.js qa",
"preqa:screenshot": "npm run build:ts",
"qa:screenshot": "node scripts/capture-ui-screenshot.js",
"pree2e:playwright": "npm run build:ts && npm run build:css && npm run build:webpack",
"e2e:playwright": "playwright test -c playwright.config.ts",
"e2e:playwright:ci": "playwright test -c playwright.config.ts",
"e2e:playwright:headed": "playwright test -c playwright.config.ts --headed",
"predocs:screenshots": "npm run build:ts && npm run build:webpack",
"docs:screenshots": "node scripts/generate-doc-screenshots.js",
"security": "node scripts/index.js security",
Expand Down
22 changes: 22 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig } from 'playwright/test';

export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
workers: 2,
timeout: 120_000,
expect: {
timeout: 15_000,
},
reporter: [['list'], ['html', { open: 'never', outputFolder: 'dist/qa/playwright-report' }]],
outputDir: 'dist/qa/playwright-results',
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 15_000,
navigationTimeout: 30_000,
},
});
24 changes: 24 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { app, BrowserWindow, dialog, ipcMain, net, protocol } from 'electron';
import { autoUpdater } from 'electron-updater';
import fs from 'fs';
import os from 'node:os';
import path from 'path';
import { pathToFileURL } from 'node:url';
import yaml from 'yaml';
Expand Down Expand Up @@ -59,6 +60,29 @@ const createForbiddenAssetResponse = (): Response => new Response('Forbidden', {

// Set environment
const isDevelopment = process.env.NODE_ENV === 'development';
const e2eUserDataPath = process.env.ELECTRON_USER_DATA_PATH;
const isPathWithinTempRoot = (candidatePath: string): boolean => {
const tempRootPath = path.resolve(os.tmpdir());
const resolvedCandidatePath = path.resolve(candidatePath);
const relativePath = path.relative(tempRootPath, resolvedCandidatePath);

return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
};

if (
process.env.NODE_ENV === 'test' &&
typeof e2eUserDataPath === 'string' &&
e2eUserDataPath.trim().length > 0
) {
const resolvedUserDataPath = path.resolve(e2eUserDataPath);

if (isPathWithinTempRoot(resolvedUserDataPath)) {
fs.mkdirSync(resolvedUserDataPath, { recursive: true });
app.setPath('userData', resolvedUserDataPath);
} else {
console.warn(`Ignoring ELECTRON_USER_DATA_PATH outside temp root: ${resolvedUserDataPath}`);
}
}

async function createWindow() {
// Create the browser window
Expand Down
8 changes: 8 additions & 0 deletions tests/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Purpose: quick map of what is covered, why it exists, and which command to run.
- Full tests: `npm test -- --runInBand`
- Lint: `npm run lint`
- Markdown docs lint (links/images/icons): `npm run lint:md`
- Electron E2E (Playwright): `npm run e2e:playwright`
- UI screenshot gate: `npm run qa:screenshot`
- Docs screenshots: `npm run docs:screenshots`
- Devcontainer smoke: `devcontainer up --workspace-folder .` then `devcontainer exec --workspace-folder . npm run lint`
Expand Down Expand Up @@ -40,6 +41,12 @@ Purpose: quick map of what is covered, why it exists, and which command to run.
| `tests/integration/main-process/xml-export-e2e.test.ts` | XML export pipeline | End-to-end XML shape, CDATA wrapping, invalid-character sanitization, summary metrics |
| `tests/integration/pattern-merging.test.ts` | Filtering + gitignore merge behavior | Combined behavior of include/exclude patterns with gitignore toggles |

## Electron E2E Tests

| File | Primary Target | Key Use Cases |
| ----------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `tests/e2e/electron-process-flow.spec.ts` | Full renderer + preload + main-process wiring | Folder selection, file tree interaction, process flow, XML format handling, refresh-from-disk behavior, save flow |

## Visual Regression Signal

| Command | Primary Target | Key Use Cases |
Expand All @@ -63,6 +70,7 @@ Purpose: quick map of what is covered, why it exists, and which command to run.
- Renderer flow changes:
- `tests/unit/components/app.test.tsx`
- `tests/unit/components/config-tab.test.tsx`
- `tests/e2e/electron-process-flow.spec.ts`
- Main process / IPC changes:
- `tests/integration/main-process/handlers.test.ts`
- `tests/unit/main/updater.test.ts`
Expand Down
Loading
Loading