wrolpi/app/cypress/support/commands.js

84 lines
4 KiB
JavaScript
Raw Permalink Normal View History

fix: repair CI after the dependency bumps, and align it with the Trixie build (#651) * fix(app): depend on @testing-library/dom directly jest-dom 7 moved @testing-library/dom from a bundled dependency to a peer dependency. Nothing else pulls the top-level copy in — @testing-library/react 13 carries its own nested v8 — so it was present only as a peer, and CI installs with --legacy-peer-deps, which skips peers. Every suite then failed to load setupTests.js with "Cannot find module '@testing-library/dom'". Declare it as a devDependency so it installs under either resolution mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(app): import mount from cypress/react Cypress 14 removed the cypress/react18 subpath; cypress/react mounts React 18 itself. The e2e support file loads commands.js, so the stale import broke bundling for every spec, not just component tests: Module not found: Package path ./react18 is not exported from package cypress Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: install and cache Cypress the way the bump requires The Cypress binary lives in ~/.cache/Cypress, outside node_modules, but only node_modules was cached. A restored cache satisfied npm, so npm skipped the postinstall download and the 15.x binary was never fetched: The cypress npm package is installed, but the Cypress binary is missing. Cache both paths and install the binary explicitly. Also align the JS toolchain with what actually ships. Debian 13 devices run nodejs 20.19.2 and install with a plain `npm install`; the Docker app image does the same. CI pinned older Node and passed --legacy-peer-deps, which skips peer dependencies -- that is why jest-dom 7's peer requirement passed review and broke CI. Match the devices instead. Pin caddy to 2.11.4, the version Debian 13 devices run, rather than latest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: test on Python 3.13, the version that ships Debian 13 devices run Python 3.13.5 and the api/controller/help images are python:3.13-trixie, but CI ran 3.11 (and one job on 3.12), so the runtime under test was two minors behind the runtime in production. Verified on 3.13 before switching: backend 1712 passed, 5 skipped; controller 883 passed. The only new warning is SQLAlchemy 1.3.22 reaching Python's deprecated datetime.utcfromtimestamp(), which is noise rather than a break. Drop api-tests-3-12: no workflow referenced it, it duplicated api-tests, and its "Sanic does not yet support 3.12" note is stale now that devices run 3.13. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:01:52 -06:00
import {mount} from 'cypress/react'
fix: repair CI after the dependency bumps, and align it with the Trixie build (#651) * fix(app): depend on @testing-library/dom directly jest-dom 7 moved @testing-library/dom from a bundled dependency to a peer dependency. Nothing else pulls the top-level copy in — @testing-library/react 13 carries its own nested v8 — so it was present only as a peer, and CI installs with --legacy-peer-deps, which skips peers. Every suite then failed to load setupTests.js with "Cannot find module '@testing-library/dom'". Declare it as a devDependency so it installs under either resolution mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(app): import mount from cypress/react Cypress 14 removed the cypress/react18 subpath; cypress/react mounts React 18 itself. The e2e support file loads commands.js, so the stale import broke bundling for every spec, not just component tests: Module not found: Package path ./react18 is not exported from package cypress Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: install and cache Cypress the way the bump requires The Cypress binary lives in ~/.cache/Cypress, outside node_modules, but only node_modules was cached. A restored cache satisfied npm, so npm skipped the postinstall download and the 15.x binary was never fetched: The cypress npm package is installed, but the Cypress binary is missing. Cache both paths and install the binary explicitly. Also align the JS toolchain with what actually ships. Debian 13 devices run nodejs 20.19.2 and install with a plain `npm install`; the Docker app image does the same. CI pinned older Node and passed --legacy-peer-deps, which skips peer dependencies -- that is why jest-dom 7's peer requirement passed review and broke CI. Match the devices instead. Pin caddy to 2.11.4, the version Debian 13 devices run, rather than latest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: test on Python 3.13, the version that ships Debian 13 devices run Python 3.13.5 and the api/controller/help images are python:3.13-trixie, but CI ran 3.11 (and one job on 3.12), so the runtime under test was two minors behind the runtime in production. Verified on 3.13 before switching: backend 1712 passed, 5 skipped; controller 883 passed. The only new warning is SQLAlchemy 1.3.22 reaching Python's deprecated datetime.utcfromtimestamp(), which is noise rather than a break. Drop api-tests-3-12: no workflow referenced it, it duplicated api-tests, and its "Sanic does not yet support 3.12" note is stale now that devices run 3.13. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:01:52 -06:00
// Cypress 14 dropped the separate cypress/react18 entry point; cypress/react mounts React 18 itself.
Cypress.Commands.add('mount', mount);
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
/*
* Waiting on WROLPi background work.
*
* Most mutating file endpoints only QUEUE a job for the background file worker (move, refresh),
* and /api/files/worker_status reads 'idle' while jobs are still waiting in its queue, so
* neither the response nor the status endpoint says anything about completion. The reliable
* signal is the completion event on /api/events/feed. The pattern: capture the server's clock
* BEFORE starting the work, then poll the feed for a matching event newer than that timestamp.
*
* Events carry no job id, so the match is by name/message and cannot prove WHICH job finished.
* A test that leaves a job queued (e.g. rename queues a parent-directory refresh) must drain
* it before ending, or the next test's wait may be satisfied by the straggler.
*/
// The API decides what time it is (the frontend does the same, in case of clock drift).
Cypress.Commands.add('serverNow', () =>
cy.request('/api/events/feed').its('body.now'));
/*
* cy.waitForEvent({since, event, message?, messagePrefix?, messageContains?, failureEvent?, timeout?})
*
* Poll the events feed until an event newer than `since` matches. `event` is a name or a
* list of names; narrow further with an exact `message`, a `messagePrefix`, or a
* `messageContains` substring. If a `failureEvent` (name or list) shows up first, fail
* immediately with its message instead of timing out.
*
* cy.serverNow().then((since) => {
* // ...start the background work...
* cy.waitForEvent({since, event: 'file_move_completed', failureEvent: 'file_move_failed'});
* });
*/
Cypress.Commands.add('waitForEvent', ({since, event, message, messagePrefix, messageContains, failureEvent, timeout = 60000}) => {
const wanted = [].concat(event);
const failures = failureEvent ? [].concat(failureEvent) : [];
const matches = (e) => {
if (!wanted.includes(e.event)) return false;
if (message) return e.message === message;
if (messagePrefix) return (e.message || '').startsWith(messagePrefix);
if (messageContains) return (e.message || '').includes(messageContains);
return true;
};
const deadline = Date.now() + timeout;
const poll = () => {
cy.request(`/api/events/feed?after=${encodeURIComponent(since)}`).then(({body}) => {
const events = body.events || [];
const failed = events.find((e) => failures.includes(e.event));
expect(failed, failed && `${failed.event}: ${failed.message}`).to.be.undefined;
if (events.some(matches)) return;
expect(Date.now(), `event '${wanted}' not seen within ${timeout}ms -- `
+ 'the file worker may be wedged (restart the api service)').to.be.lessThan(deadline);
cy.wait(1000);
poll();
});
};
poll();
});
/*
* cy.refreshFiles(paths, options) -- refresh files on the live stack and wait for completion.
*
* cy.refreshFiles(['cypress-fb']); // refresh one directory
* cy.refreshFiles([], {timeout: 300000}); // global refresh, longer timeout
*/
Cypress.Commands.add('refreshFiles', (paths = [], {timeout = 60000} = {}) => {
// Refresh-completed event names, by scope (wrolpi/files/worker.py).
const completionEvents = ['directory_refresh', 'files_refreshed', 'global_after_refresh_completed'];
// A single directory announces itself by name; match exactly so another test's refresh
// finishing at the same moment cannot satisfy this wait.
const singleDirectory = paths.length === 1 && !/\.[^/]+$/.test(paths[0]);
const message = singleDirectory ? `Refreshed: ${paths[0].replace(/\/$/, '')}` : undefined;
cy.serverNow().then((since) => {
const body = paths.length ? {paths} : undefined;
cy.request('POST', '/api/files/refresh', body);
cy.waitForEvent({since, event: completionEvents, message, timeout});
});
});