wrolpi/app/cypress.config.js

125 lines
5 KiB
JavaScript
Raw Permalink Normal View History

test: the component suite has not run since cypress 15, on any machine (#661) * test: the component suite has not run since cypress 15, on any machine Six component spec files -- 334 tests, including the 4,300-line layout suite -- could not start. Cypress exited before printing anything, and under its default Electron browser it did not even do that: it hung at 0% CPU until killed. Chrome prints the error in seconds, which is the only reason this was findable. Three things broke, all of them from the 13.17.0 -> 15.20.0 bump. `baseUrl: "src"`. Cypress 15 loads its config with tsx, and tsx honours tsconfig path resolution. Cypress requires Node's core `events` module; with that baseUrl the bare specifier resolves against `src`, and on a case-insensitive filesystem it matches `src/Events.js`. That file imports the app, so the whole component tree was required in Node: JSX in `.js` files, which esbuild will not parse. This is why CI stayed green while every local run died. Linux is case-sensitive, `events` never matched `Events.js`, and it resolved to the built-in. The baseUrl buys nothing -- there is not one absolute import in the codebase -- so it goes. `framework: "create-react-app"`. Cypress 14 removed that dev server; the handler is in the 13.17.0 binary and absent from 15. The webpack config is handed over explicitly now, taken from react-scripts so components compile exactly as `npm start` compiles them. Two things follow from that: `webpack-dev-server` 5 is a devDependency because Cypress requires 5 and react-scripts pins 4 (npm nests react-scripts' copy, and the production build is unchanged), and babel-loader is widened to `cypress/`, which react-scripts does not compile -- the support file is JSX and was reaching babel with no preset. The suite ran nowhere. `cypress run` means e2e -- its specPattern is `cypress/e2e/**` -- and these specs sit beside the components in `src`, so no job has ever executed them. A `cypress-component-tests` job runs them now; without it this breaks again in silence. One test fails the moment the suite can run, and it is mine, from #660: it asserted the URL column is the widest in the row. It is not, and never was -- at that width the fixed columns take ~307px of 400, leaving the URL ~93 against Control's 146. It shipped green because nothing could run it. It asserts a usable remainder instead. 334 component tests pass, 69 suites / 1214 jest tests pass, `npm run build` is clean. * review: an assertion with a floor, and comments that told the story instead of the rule The leftover-width test had a lower bound and nothing else, so it passed on any column wide enough to have padding -- including one that had grown, which is what losing table-layout: fixed does. It measures the leftover now: the sized columns are held to the widths they declare, and the truncating column to exactly what they did not take. Comments in the config, the CI job and the spec cut back to the rule a reader needs. The version history and the measurements are in the PR.
2026-08-12 21:57:07 -06:00
const path = require("path");
e2e: exercise the FileBrowser footer buttons against real files (#670) * e2e: exercise the FileBrowser footer buttons against real files Sixteen Cypress tests covering Rename, Move, Delete (including the tagged-file force-delete flow), Ignore/Unignore, Upload, and scoped Refresh, with no API mocking: fb:* tasks seed a cypress-fb/ sandbox inside the media directory, the UI mutates it through the real API, and assertions check the disk as well as the table. Skipped in CI, which runs only the React dev server. Background jobs (move, refresh) only queue work and worker_status reads idle while jobs wait, so cy.refreshFiles / cy.waitForEvent poll the events feed for completion events newer than the server's clock, and fail loudly when one never arrives. Every suite cleans up unconditionally: sandbox files, DB rows via a final refresh, test tags and their tags/ directories, and any leaked ignored-directory config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * e2e: act on review — drain stragglers, teardown that cannot be skipped Events carry no job id, so waitForEvent cannot prove WHICH job finished. The rename tests now drain the parent-directory refresh their rename queues before ending, so the next test's refresh wait cannot be satisfied by a straggler while the DB still reflects the old tree; move waits match the destination in the completion message. Teardown must always reach the disk wipe: a failed command aborts the rest of its hook, so the tag cleanup and the DB-purge refresh are now best-effort (failOnStatusCode: false, no assertions), and the unignore runs after every test instead of once per spec. History and forward-looking product notes trimmed from comments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 20:26:46 -06:00
const fs = require("fs");
const { defineConfig } = require("cypress");
e2e: exercise the FileBrowser footer buttons against real files (#670) * e2e: exercise the FileBrowser footer buttons against real files Sixteen Cypress tests covering Rename, Move, Delete (including the tagged-file force-delete flow), Ignore/Unignore, Upload, and scoped Refresh, with no API mocking: fb:* tasks seed a cypress-fb/ sandbox inside the media directory, the UI mutates it through the real API, and assertions check the disk as well as the table. Skipped in CI, which runs only the React dev server. Background jobs (move, refresh) only queue work and worker_status reads idle while jobs wait, so cy.refreshFiles / cy.waitForEvent poll the events feed for completion events newer than the server's clock, and fail loudly when one never arrives. Every suite cleans up unconditionally: sandbox files, DB rows via a final refresh, test tags and their tags/ directories, and any leaked ignored-directory config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * e2e: act on review — drain stragglers, teardown that cannot be skipped Events carry no job id, so waitForEvent cannot prove WHICH job finished. The rename tests now drain the parent-directory refresh their rename queues before ending, so the next test's refresh wait cannot be satisfied by a straggler while the DB still reflects the old tree; move waits match the destination in the completion message. Teardown must always reach the disk wipe: a failed command aborts the rest of its hook, so the tag cleanup and the DB-purge refresh are now best-effort (failOnStatusCode: false, no assertions), and the unignore runs after every test instead of once per spec. History and forward-looking product notes trimmed from comments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 20:26:46 -06:00
/*
* Real-file tasks for the FileBrowser e2e tests.
*
* The tests exercise the live stack (delete/rename/move really happen), so every task is
* jailed to one sandbox directory inside the media directory. `test/` also holds shared
* fixtures the whole suite depends on -- a task that could write or delete outside the
* sandbox would let a buggy test destroy them.
*/
const MEDIA_DIR = process.env.WROLPI_MEDIA_DIR || path.resolve(__dirname, "..", "test");
const SANDBOX_NAME = "cypress-fb";
const SANDBOX_DIR = path.join(MEDIA_DIR, SANDBOX_NAME);
function sandboxPath(relativePath) {
const resolved = path.resolve(SANDBOX_DIR, relativePath || ".");
if (resolved !== SANDBOX_DIR && !resolved.startsWith(SANDBOX_DIR + path.sep)) {
throw new Error(`Refusing to touch path outside the sandbox: ${relativePath}`);
}
return resolved;
}
const fileBrowserTasks = {
// Wipe the sandbox (including leftovers from a crashed run) and create the given files.
// `files` maps sandbox-relative paths to file content; a trailing '/' makes a bare directory.
"fb:reset": ({ files }) => {
fs.rmSync(SANDBOX_DIR, { recursive: true, force: true });
fs.mkdirSync(SANDBOX_DIR, { recursive: true });
for (const [relativePath, content] of Object.entries(files || {})) {
if (relativePath.endsWith("/")) {
fs.mkdirSync(sandboxPath(relativePath), { recursive: true });
} else {
const filePath = sandboxPath(relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content ?? "");
}
}
return SANDBOX_NAME;
},
// Add files to the existing sandbox without wiping it (e.g. behind the UI's back).
"fb:seed": ({ files }) => {
for (const [relativePath, content] of Object.entries(files || {})) {
const filePath = sandboxPath(relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content ?? "");
}
return true;
},
"fb:exists": (relativePath) => fs.existsSync(sandboxPath(relativePath)),
"fb:readdir": (relativePath) => {
const dir = sandboxPath(relativePath);
return fs.existsSync(dir) ? fs.readdirSync(dir).sort() : null;
},
"fb:cleanup": () => {
fs.rmSync(SANDBOX_DIR, { recursive: true, force: true });
return true;
},
// Tagging creates a hardlink under <media>/tags/<TagName>/, and a force-deleted file
// leaves that link behind (the tags-directory sync refuses to remove it). Only tag
// directories created by these tests may be removed: the name must start with 'CypressFB'.
"fb:cleanup-tag-dir": (tagName) => {
if (!/^CypressFB[A-Za-z0-9]*$/.test(tagName)) {
throw new Error(`Refusing to remove tag directory not owned by the tests: ${tagName}`);
}
fs.rmSync(path.join(MEDIA_DIR, "tags", tagName), { recursive: true, force: true });
return true;
},
};
module.exports = defineConfig({
e2e: {
e2e: exercise the FileBrowser footer buttons against real files (#670) * e2e: exercise the FileBrowser footer buttons against real files Sixteen Cypress tests covering Rename, Move, Delete (including the tagged-file force-delete flow), Ignore/Unignore, Upload, and scoped Refresh, with no API mocking: fb:* tasks seed a cypress-fb/ sandbox inside the media directory, the UI mutates it through the real API, and assertions check the disk as well as the table. Skipped in CI, which runs only the React dev server. Background jobs (move, refresh) only queue work and worker_status reads idle while jobs wait, so cy.refreshFiles / cy.waitForEvent poll the events feed for completion events newer than the server's clock, and fail loudly when one never arrives. Every suite cleans up unconditionally: sandbox files, DB rows via a final refresh, test tags and their tags/ directories, and any leaked ignored-directory config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * e2e: act on review — drain stragglers, teardown that cannot be skipped Events carry no job id, so waitForEvent cannot prove WHICH job finished. The rename tests now drain the parent-directory refresh their rename queues before ending, so the next test's refresh wait cannot be satisfied by a straggler while the DB still reflects the old tree; move waits match the destination in the completion message. Teardown must always reach the disk wipe: a failed command aborts the rest of its hook, so the tag cleanup and the DB-purge refresh are now best-effort (failOnStatusCode: false, no assertions), and the unignore runs after every test instead of once per spec. History and forward-looking product notes trimmed from comments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 20:26:46 -06:00
setupNodeEvents(on) {
on("task", fileBrowserTasks);
},
// Use HTTP for CI, HTTPS for local development
baseUrl: process.env.CI ? 'http://localhost:3000' : 'https://localhost:8443',
specPattern: 'cypress/e2e/**/*.cy.js',
supportFile: 'cypress/support/e2e.js',
video: process.env.CI ? true : false,
screenshotOnRunFailure: true,
2026-03-10 20:26:00 -06:00
env: {
CI: !!process.env.CI,
},
},
component: {
devServer: {
test: the component suite has not run since cypress 15, on any machine (#661) * test: the component suite has not run since cypress 15, on any machine Six component spec files -- 334 tests, including the 4,300-line layout suite -- could not start. Cypress exited before printing anything, and under its default Electron browser it did not even do that: it hung at 0% CPU until killed. Chrome prints the error in seconds, which is the only reason this was findable. Three things broke, all of them from the 13.17.0 -> 15.20.0 bump. `baseUrl: "src"`. Cypress 15 loads its config with tsx, and tsx honours tsconfig path resolution. Cypress requires Node's core `events` module; with that baseUrl the bare specifier resolves against `src`, and on a case-insensitive filesystem it matches `src/Events.js`. That file imports the app, so the whole component tree was required in Node: JSX in `.js` files, which esbuild will not parse. This is why CI stayed green while every local run died. Linux is case-sensitive, `events` never matched `Events.js`, and it resolved to the built-in. The baseUrl buys nothing -- there is not one absolute import in the codebase -- so it goes. `framework: "create-react-app"`. Cypress 14 removed that dev server; the handler is in the 13.17.0 binary and absent from 15. The webpack config is handed over explicitly now, taken from react-scripts so components compile exactly as `npm start` compiles them. Two things follow from that: `webpack-dev-server` 5 is a devDependency because Cypress requires 5 and react-scripts pins 4 (npm nests react-scripts' copy, and the production build is unchanged), and babel-loader is widened to `cypress/`, which react-scripts does not compile -- the support file is JSX and was reaching babel with no preset. The suite ran nowhere. `cypress run` means e2e -- its specPattern is `cypress/e2e/**` -- and these specs sit beside the components in `src`, so no job has ever executed them. A `cypress-component-tests` job runs them now; without it this breaks again in silence. One test fails the moment the suite can run, and it is mine, from #660: it asserted the URL column is the widest in the row. It is not, and never was -- at that width the fixed columns take ~307px of 400, leaving the URL ~93 against Control's 146. It shipped green because nothing could run it. It asserts a usable remainder instead. 334 component tests pass, 69 suites / 1214 jest tests pass, `npm run build` is clean. * review: an assertion with a floor, and comments that told the story instead of the rule The leftover-width test had a lower bound and nothing else, so it passed on any column wide enough to have padding -- including one that had grown, which is what losing table-layout: fixed does. It measures the leftover now: the sized columns are held to the widths they declare, and the truncating column to exactly what they did not take. Comments in the config, the CI job and the spec cut back to the rule a reader needs. The version history and the measurements are in the PR.
2026-08-12 21:57:07 -06:00
// Cypress removed its create-react-app framework, so hand it the same webpack config
// `npm start` uses, minus the entry, output and HTML plugins Cypress supplies itself.
framework: "react",
bundler: "webpack",
test: the component suite has not run since cypress 15, on any machine (#661) * test: the component suite has not run since cypress 15, on any machine Six component spec files -- 334 tests, including the 4,300-line layout suite -- could not start. Cypress exited before printing anything, and under its default Electron browser it did not even do that: it hung at 0% CPU until killed. Chrome prints the error in seconds, which is the only reason this was findable. Three things broke, all of them from the 13.17.0 -> 15.20.0 bump. `baseUrl: "src"`. Cypress 15 loads its config with tsx, and tsx honours tsconfig path resolution. Cypress requires Node's core `events` module; with that baseUrl the bare specifier resolves against `src`, and on a case-insensitive filesystem it matches `src/Events.js`. That file imports the app, so the whole component tree was required in Node: JSX in `.js` files, which esbuild will not parse. This is why CI stayed green while every local run died. Linux is case-sensitive, `events` never matched `Events.js`, and it resolved to the built-in. The baseUrl buys nothing -- there is not one absolute import in the codebase -- so it goes. `framework: "create-react-app"`. Cypress 14 removed that dev server; the handler is in the 13.17.0 binary and absent from 15. The webpack config is handed over explicitly now, taken from react-scripts so components compile exactly as `npm start` compiles them. Two things follow from that: `webpack-dev-server` 5 is a devDependency because Cypress requires 5 and react-scripts pins 4 (npm nests react-scripts' copy, and the production build is unchanged), and babel-loader is widened to `cypress/`, which react-scripts does not compile -- the support file is JSX and was reaching babel with no preset. The suite ran nowhere. `cypress run` means e2e -- its specPattern is `cypress/e2e/**` -- and these specs sit beside the components in `src`, so no job has ever executed them. A `cypress-component-tests` job runs them now; without it this breaks again in silence. One test fails the moment the suite can run, and it is mine, from #660: it asserted the URL column is the widest in the row. It is not, and never was -- at that width the fixed columns take ~307px of 400, leaving the URL ~93 against Control's 146. It shipped green because nothing could run it. It asserts a usable remainder instead. 334 component tests pass, 69 suites / 1214 jest tests pass, `npm run build` is clean. * review: an assertion with a floor, and comments that told the story instead of the rule The leftover-width test had a lower bound and nothing else, so it passed on any column wide enough to have padding -- including one that had grown, which is what losing table-layout: fixed does. It measures the leftover now: the sized columns are held to the widths they declare, and the truncating column to exactly what they did not take. Comments in the config, the CI job and the spec cut back to the rule a reader needs. The version history and the measurements are in the PR.
2026-08-12 21:57:07 -06:00
webpackConfig: () => {
// react-scripts reads both of these at require time.
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
process.env.BABEL_ENV = process.env.BABEL_ENV || 'development';
const config = require('react-scripts/config/webpack.config')('development');
delete config.entry;
delete config.output;
delete config.optimization;
const cypressOwnsThese = [
'HtmlWebpackPlugin', 'InterpolateHtmlPlugin', 'MiniCssExtractPlugin',
'WebpackManifestPlugin', 'ManifestPlugin', 'ModuleNotFoundPlugin',
'ForkTsCheckerWebpackPlugin',
];
config.plugins = (config.plugins || [])
.filter(plugin => !cypressOwnsThese.includes(plugin.constructor.name));
// react-scripts only babel-loads `src`; the JSX support file lives in `cypress/`.
const alsoCompile = [path.resolve(__dirname, 'cypress')];
const widenBabel = (rules) => rules.forEach((rule) => {
if (rule.oneOf) return widenBabel(rule.oneOf);
const loader = String(rule.loader || '');
if (loader.includes('babel-loader') && rule.include) {
rule.include = [].concat(rule.include, alsoCompile);
}
});
widenBabel(config.module.rules);
return config;
},
},
},
});