mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Add Playwright/bootRun/test.sh tasks (#6244)
## Description Consolidates Playwright running under cohesive Task namespaces, isolates Playwright state from the developer's local working tree, and swaps CI's frontend webserver from `vite` dev to `vite preview` against a pre-built `dist/`. ### `e2e:*` namespace Renames `.taskfiles/testing.yml` to `.taskfiles/e2e.yml` and consolidates everything Playwright-related under one `e2e:` namespace: - `e2e:stubbed` / `e2e:live` / `e2e:enterprise` / `e2e:cross-browser`: project-specific runners - `e2e:check` (no-Docker subset) and `e2e:check:all` (full) - `e2e:oauth:up` / `:down`, `e2e:saml:up` / `:down`: symmetric lifecycle for the keycloak compose stacks - `e2e:install`: Playwright browser install - `docker:test`: full Docker integration suite The redundant `frontend:test:e2e:*` project shortcuts are removed. CI workflows (`e2e-stubbed.yml`, `e2e-live.yml`, `build-enterprise.yml`, `nightly.yml`) are updated to call the new task names. ### Isolated Playwright state New `STIRLING_BASE_PATH` (and `-Dstirling.base-path=`) override in `InstallationPathConfig` redirects the entire state tree (configs, backups, customFiles, pipeline, logs) at startup. `task e2e:live` points it at `.test-state/playwright/` (purged on every invocation) so the suite never touches the developer's local DB, settings.yml or backups. `task e2e:live` auto-spawns gradle, waits for `/api/v1/info/status` to come up, runs Playwright, then tears down the whole backend process tree. ### CI runs Playwright against `vite preview` Builds the frontend up-front with `VITE_BUILD_FOR_PREVIEW=1` (forces absolute base so deep SPA routes resolve `/assets/...`) and the playwright `webServer` now uses `vite preview --port 5173 --strictPort` in CI. Avoids the per-page on-demand transform cost that was blowing the 30s navigation timeout under `--workers=3` on `all-tool-pages-load.spec.ts`. Local dev keeps `vite` dev for HMR. ### OAuth/SAML compose helpers `start-oauth-test.sh` and `start-saml-test.sh` gain a `--license-key <KEY>` (`-k`) flag so CI and scripted runs can skip the interactive license prompt. `start-oauth-test.sh` also moves from `for arg in "$@"` to a `while`-with-`shift` arg loop to support multi-arg flags consistently with the SAML script. ### Backend gradlew unification Drops the per-platform `cmd /c gradlew.bat` branches from `backend.yml` and routes every gradle invocation through `bash gradlew`. Works uniformly on Linux/macOS and Windows-with-Git-Bash. ### Compare.tsx flake fix (re-land of [#6316](https://github.com/Stirling-Tools/Stirling-PDF/pull/6316)) Piggybacks Anthony's never-merged fix from #6316. Without it, `e2e:stubbed` continues to flake under `--workers=3` on `compare.spec.ts`'s second-upload case via a React "Maximum update depth exceeded" infinite loop in the Compare auto-fill effect. CI traces from recent failed runs match exactly; 10 local runs of `compare.spec.ts` with `CI=1 --workers=3` pass cleanly with the fix applied. --------- Co-authored-by: James Brunton <[email protected]>
This commit is contained in:
co-authored by
James Brunton
parent
c059e13423
commit
f60a075443
@@ -96,7 +96,13 @@ export default defineConfig({
|
||||
],
|
||||
|
||||
webServer: {
|
||||
command: "npx vite",
|
||||
// In CI, serve a pre-built `dist/` via `vite preview` so the heavy tool
|
||||
// pages don't pay vite's on-demand transform cost on first hit (which
|
||||
// blew the 30s navigationTimeout under --workers=3 — see
|
||||
// all-tool-pages-load.spec.ts). Locally, keep `vite` dev for HMR.
|
||||
command: process.env.CI
|
||||
? "npx vite preview --port 5173 --strictPort"
|
||||
: "npx vite",
|
||||
url: "http://localhost:5173",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
|
||||
@@ -157,33 +157,39 @@ const Compare = (props: BaseToolProps) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Track previous file count to detect the transition to exactly 2 files.
|
||||
const prevAllIdsLengthRef = useRef<number | null>(null);
|
||||
|
||||
// Auto-fill slots when the file count first reaches 2; respect manual picker changes after that.
|
||||
// Keep slot params in sync with the current workbench/selection state.
|
||||
//
|
||||
// The effect must be idempotent: it can fire repeatedly during an upload
|
||||
// burst (ADD_FILES, SET_SELECTED_FILES, and downstream dispatches all land
|
||||
// in quick succession), and any "did the count transition?" tracking via
|
||||
// a ref races with React's batched re-renders under load. With workers=3
|
||||
// in CI, that race surfaced as a "Maximum update depth exceeded" crash
|
||||
// when the second slot was filled. So the implementation derives the
|
||||
// target params purely from current state and bails when there's nothing
|
||||
// to change.
|
||||
useEffect(() => {
|
||||
const selectedIds = fileState.ui.selectedFileIds as FileId[];
|
||||
const allIds = fileState.files.ids as FileId[];
|
||||
const prevLength = prevAllIdsLengthRef.current;
|
||||
prevAllIdsLengthRef.current = allIds.length;
|
||||
|
||||
if (allIds.length === 2 && prevLength !== 2) {
|
||||
// Transitioned to exactly 2 files — auto-fill both slots.
|
||||
const [firstId, secondId] = allIds as [FileId, FileId];
|
||||
fileActions.setSelectedFiles([firstId, secondId]);
|
||||
base.params.setParameters((prev) => {
|
||||
if (prev.baseFileId === firstId && prev.comparisonFileId === secondId)
|
||||
return prev;
|
||||
return { ...prev, baseFileId: firstId, comparisonFileId: secondId };
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedIds.length > 2) {
|
||||
fileActions.setSelectedFiles(selectedIds.slice(0, 2) as FileId[]);
|
||||
return;
|
||||
}
|
||||
|
||||
// When exactly 2 files are loaded but the selection doesn't cover both,
|
||||
// promote the selection so Compare can run. Skips when the selection is
|
||||
// already correct so we don't dispatch a fresh array reference and
|
||||
// retrigger this effect. Covers the upload window where ADD_FILES has
|
||||
// landed but SET_SELECTED_FILES is still in flight.
|
||||
if (allIds.length === 2) {
|
||||
const selectionMatchesAllIds =
|
||||
selectedIds.length === 2 &&
|
||||
allIds.every((id) => selectedIds.includes(id));
|
||||
if (!selectionMatchesAllIds) {
|
||||
fileActions.setSelectedFiles([...allIds] as FileId[]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky slot mapping: preserve the current slot if its file is still selected,
|
||||
// otherwise fill empty slots from the selection in order. This lets users add/remove
|
||||
// files from the selection without the other slot being clobbered.
|
||||
|
||||
+34
-13
@@ -49,6 +49,21 @@ export default defineConfig(async ({ mode }) => {
|
||||
xfwd: true,
|
||||
};
|
||||
|
||||
// Shared between `vite` (dev) and `vite preview` (production-build serve, used
|
||||
// in CI/E2E) so the live test suite still resolves /api → :8080.
|
||||
const backendProxyConfig =
|
||||
effectiveMode === "desktop"
|
||||
? undefined
|
||||
: {
|
||||
"/api": backendProxy,
|
||||
"/oauth2": backendProxy,
|
||||
"/saml2": backendProxy,
|
||||
"/login/oauth2": backendProxy,
|
||||
"/login/saml2": backendProxy,
|
||||
"/swagger-ui": backendProxy,
|
||||
"/v1/api-docs": backendProxy,
|
||||
};
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
react(),
|
||||
@@ -107,19 +122,25 @@ export default defineConfig(async ({ mode }) => {
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
// Only use proxy in web mode - Tauri handles backend connections directly
|
||||
proxy:
|
||||
effectiveMode === "desktop"
|
||||
? undefined
|
||||
: {
|
||||
"/api": backendProxy,
|
||||
"/oauth2": backendProxy,
|
||||
"/saml2": backendProxy,
|
||||
"/login/oauth2": backendProxy,
|
||||
"/login/saml2": backendProxy,
|
||||
"/swagger-ui": backendProxy,
|
||||
"/v1/api-docs": backendProxy,
|
||||
},
|
||||
proxy: backendProxyConfig,
|
||||
},
|
||||
base: env.RUN_SUBPATH ? `/${env.RUN_SUBPATH}` : "./",
|
||||
preview: {
|
||||
host: true,
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
proxy: backendProxyConfig,
|
||||
},
|
||||
// base: "./" produces relative asset URLs which work when dist/ is served
|
||||
// at any path (e.g. Spring Boot bundling the frontend at /). But under
|
||||
// `vite preview` for deep SPA routes (e.g. /workflow/sign/<token>), the
|
||||
// browser resolves ./assets/X.js relative to the current path → 404, then
|
||||
// SPA fallback returns index.html as text/html and React never mounts.
|
||||
// VITE_BUILD_FOR_PREVIEW=1 (set by the CI playwright steps) overrides to
|
||||
// an absolute base so deep-route asset paths resolve to /assets/...
|
||||
base: env.RUN_SUBPATH
|
||||
? `/${env.RUN_SUBPATH}`
|
||||
: process.env.VITE_BUILD_FOR_PREVIEW === "1"
|
||||
? "/"
|
||||
: "./",
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user