diff --git a/.github/workflows/e2e-datasets.yml b/.github/workflows/e2e-datasets.yml
new file mode 100644
index 000000000..4d6f01351
--- /dev/null
+++ b/.github/workflows/e2e-datasets.yml
@@ -0,0 +1,28 @@
+# A cache saved by a pull request run is only visible to that pull request.
+# One saved on main is visible to all of them, so this keeps it there.
+name: E2E dataset cache
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ warm:
+ runs-on: ubuntu-latest
+ name: Warm the dataset cache
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ # Keep in step with the cache step in e2e.yml.
+ - name: Cache test datasets
+ uses: actions/cache@v4
+ with:
+ path: .tmp/datasets
+ key: e2e-datasets-${{ hashFiles('tests/datasets.ts') }}
+ restore-keys: e2e-datasets-
+ - name: Download missing datasets
+ run: node tests/downloadDatasets.ts
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 4993780ad..f540f0e1b 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -29,6 +29,12 @@ jobs:
run: npm ci
env:
DETECT_CHROMEDRIVER_VERSION: true
+ - name: Cache test datasets
+ uses: actions/cache@v4
+ with:
+ path: .tmp/datasets
+ key: e2e-datasets-${{ hashFiles('tests/datasets.ts') }}
+ restore-keys: e2e-datasets-
- name: Install xvfb (linux)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get install xvfb
diff --git a/eslint.config.js b/eslint.config.js
index 287844321..78aaaeafc 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -84,6 +84,38 @@ const featureBoundaries = (features) => {
];
};
+// Files that still pass their own time limits. Remove a file once it is
+// clean; never add one.
+const E2E_TIMEOUT_DEBT = [
+ 'tests/pageobjects/volview.page.ts',
+ 'tests/specs/annotationTestUtils.ts',
+ 'tests/specs/automatic-layering.e2e.ts',
+ 'tests/specs/bug-report.e2e.ts',
+ 'tests/specs/cine-drag-onto-slot.e2e.ts',
+ 'tests/specs/cine-rendering.e2e.ts',
+ 'tests/specs/cine-ruler-session.e2e.ts',
+ 'tests/specs/crosshairs-multi-image.e2e.ts',
+ 'tests/specs/delete-selected-annotation.e2e.ts',
+ 'tests/specs/dicom-dimension-mismatch.e2e.ts',
+ 'tests/specs/different-direction-labelmap.e2e.ts',
+ 'tests/specs/imageCacheUtils.ts',
+ 'tests/specs/layer-source-lifetime.e2e.ts',
+ 'tests/specs/layers-and-rendering.e2e.ts',
+ 'tests/specs/multi-series-load.e2e.ts',
+ 'tests/specs/paint-tool-rendering.e2e.ts',
+ 'tests/specs/polygon-nested-interaction.e2e.ts',
+ 'tests/specs/sample-rendering.e2e.ts',
+ 'tests/specs/save-large-labelmap.e2e.ts',
+ 'tests/specs/seg-nrrd-export.e2e.ts',
+ 'tests/specs/select-annotation-at-press.e2e.ts',
+ 'tests/specs/select-tool-annotation-press.e2e.ts',
+ 'tests/specs/session-large-uri-base.e2e.ts',
+ 'tests/specs/sparse-manifest-prostate-rectangle.e2e.ts',
+ 'tests/specs/sparse-manifest.e2e.ts',
+ 'tests/specs/ultrasound-spacing.e2e.ts',
+ 'tests/specs/vtk-interactor-lifecycle.e2e.ts',
+];
+
export default tseslint.config(
{
ignores: [
@@ -142,6 +174,27 @@ export default tseslint.config(
'class-methods-use-this': 'off',
},
},
+ // A wait with its own time limit stands in for a state the app should
+ // publish, and fails on a slow machine. Wait on the state instead.
+ {
+ files: ['**/tests/specs/**/*.ts', '**/tests/pageobjects/**/*.ts'],
+ ignores: E2E_TIMEOUT_DEBT,
+ rules: {
+ 'no-restricted-syntax': [
+ 'error',
+ {
+ selector: "Property[key.name='timeout']",
+ message:
+ 'Wait for a state the page publishes rather than passing a timeout.',
+ },
+ {
+ selector:
+ "CallExpression[callee.object.name='browser'][callee.property.name='pause']",
+ message: 'Wait for a state the page publishes rather than pausing.',
+ },
+ ],
+ },
+ },
{
files: ['**/__tests__/*.{js,ts}', '**/tests/unit/**/*.spec.{js,ts}'],
languageOptions: {
diff --git a/index.html b/index.html
index eda9801e8..d460585dc 100644
--- a/index.html
+++ b/index.html
@@ -6,14 +6,6 @@
VolView
-
-