From eda1be90c2f3062112299560bda48e1549812fc7 Mon Sep 17 00:00:00 2001 From: TechJeeper Date: Sun, 19 Jul 2026 16:56:46 +0000 Subject: [PATCH] Add TestDriver desktop (Electron) E2E test for Printventory --- .github/workflows/testdriver-desktop.yml | 53 ++++++++ package-lock.json | 8 +- package.json | 2 +- tests/electron-app.test.js | 158 +++++++++++++++++++++++ 4 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/testdriver-desktop.yml create mode 100644 tests/electron-app.test.js diff --git a/.github/workflows/testdriver-desktop.yml b/.github/workflows/testdriver-desktop.yml new file mode 100644 index 0000000..d2dbc30 --- /dev/null +++ b/.github/workflows/testdriver-desktop.yml @@ -0,0 +1,53 @@ +name: TestDriver Desktop (Electron) E2E + +# Dedicated workflow for the TestDriver computer-use test that drives the +# Printventory DESKTOP build (tests/electron-app.test.js). +# +# The app itself is cloned + installed INSIDE the TestDriver sandbox by the test, +# so the GitHub runner only needs vitest + testdriverai — NOT the app's full +# native dependencies (which require the heavy electron-builder postinstall). +# That's why this installs devDeps with --ignore-scripts instead of `npm ci`. +# +# Requires the repo secret TD_API_KEY (Settings → Secrets and variables → Actions). + +on: + workflow_dispatch: {} + # Also run when the desktop test or its deps change. + push: + paths: + - 'tests/electron-app.test.js' + - 'vitest.config.js' + - '.github/workflows/testdriver-desktop.yml' + +jobs: + desktop-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + # Install ONLY the test runner deps. --ignore-scripts avoids the app's + # electron-builder install-app-deps postinstall, which we don't need on + # the runner (the app is provisioned inside the sandbox by the test). + - name: Install test-runner dependencies + run: npm install --no-audit --no-fund --legacy-peer-deps --ignore-scripts + + - name: Run TestDriver desktop test + env: + TD_API_KEY: ${{ secrets.TD_API_KEY }} + run: npx vitest run tests/electron-app.test.js + + - name: Upload TestDriver results + if: always() + uses: actions/upload-artifact@v4 + with: + name: testdriver-desktop-results + path: | + .testdriver/** + test-results/** + retention-days: 30 + if-no-files-found: ignore diff --git a/package-lock.json b/package-lock.json index d19fb21..7948d65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "@playwright/test": "^1.57.0", "electron": "^43.1.0", "electron-builder": "^26.5.0", - "testdriverai": "^7.9.3", + "testdriverai": "^7.11.23-test", "vitest": "^4.1.10" } }, @@ -14588,9 +14588,9 @@ } }, "node_modules/testdriverai": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/testdriverai/-/testdriverai-7.9.3.tgz", - "integrity": "sha512-Rq6RG0J3c5aKVnmpFKD+VyjhZ1Qrs1dG5k70Au6YfM+T5DDgWRJIDV/NQJKeSaYx4tjDvR2OIHbuP4dVo1A6SA==", + "version": "7.11.23-test", + "resolved": "https://registry.npmjs.org/testdriverai/-/testdriverai-7.11.23-test.tgz", + "integrity": "sha512-rDQdiXMNBeYEd5Fw+tkiRyD8lT6+2uASVVZG3Yy+VlDgHFFeufZDboA6eKsoTqEtU/ZsPok59YvaatD9d8SAdg==", "dev": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index c4ba5ea..f543650 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "@playwright/test": "^1.57.0", "electron": "^43.1.0", "electron-builder": "^26.5.0", - "testdriverai": "^7.9.3", + "testdriverai": "^7.11.23-test", "vitest": "^4.1.10" }, "overrides": { diff --git a/tests/electron-app.test.js b/tests/electron-app.test.js new file mode 100644 index 0000000..4979a4b --- /dev/null +++ b/tests/electron-app.test.js @@ -0,0 +1,158 @@ +import { describe, expect, it } from 'vitest'; +import { TestDriver } from 'testdriverai/vitest/hooks'; + +/** + * Printventory — Electron DESKTOP build end-to-end test (TestDriver computer-use). + * + * Drives the REAL Electron GUI (`electron .`, i.e. `npm start`) — NOT server mode. + * + * There is no published prebuilt binary, so this test provisions the desktop + * build from source INSIDE the TestDriver sandbox: + * 1. get a Linux desktop VM + * 2. clone the repo + * 3. LEAN install — skip Puppeteer's Chromium download and the heavy + * `electron-builder install-app-deps` postinstall (which saturated the VM + * in earlier attempts); then rebuild ONLY the native module the app needs + * at boot (better-sqlite3) for Electron's ABI. + * 4. launch `electron . --no-sandbox` with PRINTVENTORY_TEST_SCAN_PATH set so + * the Scan button skips the native folder-picker dialog and scans the + * bundled fixtures directly (tests/test-fixtures/scan-me/*.stl). + * + * Then it exercises core desktop flows: + * - the app window renders (library / sidebar) + * - scanning a directory catalogs models into the grid + * - search/filter narrows the grid + * - opening a model shows its metadata/detail view + * + * NOTE: This test was authored without a live sandbox to iterate against (the + * interactive session credential was exhausted). It is expected to run in CI via + * .github/workflows/testdriver-desktop.yml, which authenticates with the + * TD_API_KEY repo secret. Element descriptions and the provisioning steps may + * need a round of tuning on the first green run. + */ + +const REPO_URL = 'https://github.com/TechJeeper/Printventory.git'; +const APP_DIR = '/home/user/Printventory'; +const SCAN_PATH = `${APP_DIR}/tests/test-fixtures/scan-me`; + +describe('Printventory desktop (Electron) app', () => { + it('scans a directory and browses the model library', async (context) => { + const testdriver = TestDriver(context); + + // --- 1. Get a Linux desktop VM ----------------------------------------- + // Provision Chrome purely to spin up the desktop environment; we then launch + // the Electron app ourselves and drive it. (We never use the browser.) + await testdriver.provision.chrome({ url: 'about:blank' }); + + // --- 2. Clone the repository ------------------------------------------- + await testdriver.exec( + 'sh', + `rm -rf ${APP_DIR} && git clone --depth 1 ${REPO_URL} ${APP_DIR} && echo CLONE_OK`, + 180000, + ); + + // --- 3. Lean install ---------------------------------------------------- + // Skip Puppeteer's Chromium download and the electron-builder postinstall + // native rebuild (both saturate the sandbox). Rely on prebuilt binaries. + await testdriver.exec( + 'sh', + [ + `cd ${APP_DIR}`, + 'export PUPPETEER_SKIP_DOWNLOAD=1', + 'export ELECTRON_CACHE=/home/user/.cache/electron', + // --ignore-scripts avoids the heavy electron-builder install-app-deps + 'npm install --no-audit --no-fund --legacy-peer-deps --ignore-scripts', + 'echo INSTALL_OK', + ].join(' && '), + 600000, + ); + + // --- 4. Rebuild ONLY better-sqlite3 for Electron's ABI ------------------ + // main.js require()s better-sqlite3 at top level, so it must match Electron's + // Node ABI. Rebuilding a single module is far lighter than a full app-deps. + await testdriver.exec( + 'sh', + [ + `cd ${APP_DIR}`, + 'export PUPPETEER_SKIP_DOWNLOAD=1', + 'npx --yes @electron/rebuild -f -o better-sqlite3 || npx --yes electron-rebuild -f -w better-sqlite3', + 'echo REBUILD_OK', + ].join(' && '), + 600000, + ); + + // --- 5. Launch the desktop build (npm start === electron .) ------------- + // PRINTVENTORY_TEST_SCAN_PATH makes the Scan button skip the native dialog + // and scan the bundled fixtures. --no-sandbox is required for Electron in CI. + await testdriver.exec( + 'sh', + [ + `cd ${APP_DIR}`, + 'export DISPLAY=:0', + `export PRINTVENTORY_TEST_SCAN_PATH="${SCAN_PATH}"`, + 'export ELECTRON_DISABLE_SANDBOX=1', + // launch detached so exec returns while the GUI keeps running + 'nohup npx electron . --no-sandbox > /tmp/printventory.log 2>&1 &', + 'sleep 3 && echo LAUNCH_OK && tail -n 20 /tmp/printventory.log || true', + ].join(' && '), + 120000, + ); + + // Give Electron time to spin up its window and load the renderer. + await testdriver.wait(15000); + await testdriver.focusApplication('Printventory'); + + // --- 6. The app window renders ----------------------------------------- + const appVisible = await testdriver.assert( + 'The Printventory desktop application window is open and its model library / sidebar interface is visible', + ); + expect(appVisible).toBeTruthy(); + + // --- 7. Scan a directory to catalog models ----------------------------- + const scanButton = await testdriver.find( + 'the "Scan Directory" button in the sidebar/toolbar', + { timeout: 30000 }, + ); + await scanButton.click(); + + // Scanning + thumbnailing takes a moment; wait for models to appear. + await testdriver.wait(20000); + + const modelsVisible = await testdriver.assert( + 'One or more 3D model cards/thumbnails are now shown in the main library grid', + ); + expect(modelsVisible).toBeTruthy(); + + // --- 8. Search / filter narrows the grid ------------------------------- + const searchInput = await testdriver.find( + 'the search / filter input box for filtering models by filename', + { timeout: 20000 }, + ); + await searchInput.click(); + await testdriver.type('cube'); + await testdriver.wait(4000); + + const filtered = await testdriver.assert( + 'The library grid has filtered to show model(s) matching the search term "cube"', + ); + expect(filtered).toBeTruthy(); + + // Clear the search so we can open a model. + await testdriver.pressKeys(['ctrl', 'a']); + await testdriver.pressKeys(['backspace']); + await testdriver.wait(3000); + + // --- 9. Open a model to view its metadata/detail ----------------------- + const firstModel = await testdriver.find( + 'the first 3D model card/thumbnail in the library grid', + { timeout: 20000 }, + ); + await firstModel.click(); + await testdriver.wait(4000); + + const detailVisible = await testdriver.assert( + 'A model detail / metadata panel is visible showing information about the selected model (such as file name, designer, tags, notes, or a 3D preview)', + ); + expect(detailVisible).toBeTruthy(); + }); +});