From ac5e0364289ce44cd941a500cc4e5ca667c9a53d Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Fri, 11 Sep 2026 16:44:33 +0000 Subject: [PATCH 01/18] Remove redundant spec --- e2e-tests/features/upload.feature | 408 ++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 e2e-tests/features/upload.feature diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature new file mode 100644 index 0000000000..4d2707b012 --- /dev/null +++ b/e2e-tests/features/upload.feature @@ -0,0 +1,408 @@ +# Uploads mutate the single shared e2e user's upload history, so scenarios must not race. +@mode:serial +Feature: Uploading images to the Grid + This lets an authorised user get images into the Grid by selecting files, + dragging and dropping files or URLs, importing Witness contributions, and + then completing the required metadata while tracking each upload's progress. + + Background: + Given the application stack is running + And I have opened the image upload page + + # --------------------------------------------------------------------------- + # Upload page shell (view.html + controller.js) + # --------------------------------------------------------------------------- + + Scenario: An authorised user sees the upload tools + Given I am permitted to upload images + When the upload page loads + Then I should see the file upload prompt + And I should see my past 50 uploads + And the drag-and-drop uploader should be active + # Evidence: kahuna/public/js/upload/view.html lines 11-12, 19-27, 31 + # Evidence: kahuna/public/js/upload/controller.js lines 40-45 + + # Needs a stack booted with enforceUploadImagesPermission=true. + @todo + Scenario: An unauthorised user is told they cannot upload + Given I am not permitted to upload images + When the upload page loads + Then I should see a message that I am not authorised to upload images + And I should see a link to email support + And I should not see the file upload prompt + And I should not see the drag-and-drop uploader + # Evidence: kahuna/public/js/upload/view.html lines 11, 29 + # Evidence: kahuna/public/js/upload/controller.js lines 36-41 + + Scenario: Returning to search from the upload page + When I choose "Back to search" from the top bar + Then I should be taken to the image search page + And my previous search should be intact + # Evidence: kahuna/public/js/upload/view.html lines 3-6 + + Scenario: The current uploads section only appears while an upload is running + Given I have an upload in progress + When the upload page loads + Then I should see my current uploads section + # Evidence: kahuna/public/js/upload/view.html lines 14-17 + # Evidence: kahuna/public/js/upload/controller.js lines 45 + + Scenario: Viewing all of my uploads + When I choose "View all your uploads" + Then I should be taken to a search filtered to images I uploaded + # Evidence: kahuna/public/js/upload/view.html lines 22-23 + + Scenario: Warning before leaving the page with uploads in progress + Given I have an upload in progress + When I try to navigate away from the upload page via the following buttons: + | Back to search | + | Home | + | View all your uploads | + Then I should be warned that uploads are in progress and asked to confirm + # Evidence: kahuna/public/js/upload/controller.js lines 18-34, 47-54 + + # --------------------------------------------------------------------------- + # File upload prompt (prompt/prompt.html + prompt.js) + # --------------------------------------------------------------------------- + + Scenario: The prompt explains how to upload + When the upload page loads + Then I should see a message telling me to drag and drop or click to upload to the system + # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 1-3 + # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 20 + + Scenario: The prompt suggests an example label when no labels are applied + Given I have not applied any preset labels + When the upload page loads + Then I should see a suggested example label to apply to all uploads + # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 + # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 21 + + @todo + Scenario: Preset labels are applied to all uploads + When I add a preset label in the prompt + Then that label should be applied to all my uploads + # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 43, 113-114 + + @todo + Scenario: Preset labels are stored in local storage + When I add preset label(s) in the prompt + Then those label(s) should already be selected when I reload the page + + # --------------------------------------------------------------------------- + # Select-files uploader (file-uploader.html + file-uploader.js) + # --------------------------------------------------------------------------- + + Scenario: Opening the file picker from the Upload button + When I click the "Upload" button + Then the system file picker should open + # Evidence: kahuna/public/js/upload/file-uploader.html lines 9-15 + # Evidence: kahuna/public/js/upload/file-uploader.js lines 41-42 + + Scenario: Selecting files queues them for upload + When I select one or more image files to upload + Then those files should be queued for upload + And I should be taken to the upload progress view + # Evidence: kahuna/public/js/upload/file-uploader.html lines 4-7 + # Evidence: kahuna/public/js/upload/file-uploader.js lines 20-26 + # Evidence: kahuna/public/js/upload/manager.js lines 68-83 + + Scenario: Files above the size limit are skipped with a warning + Given an upload size limit is configured + When I select a file that is larger than the size limit + Then I should be warned that the oversized file will be skipped + And only the files within the limit should be queued for upload + # Evidence: kahuna/public/js/upload/manager.js lines 36-47 + + # --------------------------------------------------------------------------- + # Drag-and-drop uploader (dnd-uploader.html + dnd-uploader.js) + # --------------------------------------------------------------------------- + + @todo + Scenario: Dragging valid content over the page shows the dropzone + When I drag files over the upload page + Then the dropzone overlay should appear with an explanation + # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 1-6 + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 113, 116, 158-171 + + @todo + Scenario: The dropzone hides when I stop dragging + Given the dropzone overlay is showing + When I drag away from the upload page + Then the dropzone overlay should disappear + # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 1 + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 117, 173-180 + + @todo + Scenario: Dropping files uploads them + When I drop one or more image files onto the page + Then those files should be queued for upload + And I should be taken to the upload progress view + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 33-37, 182-205 + # Evidence: kahuna/public/js/upload/manager.js lines 68-83 + + @todo + Scenario: Dropping a URL loads the image from that URL + When I drop an image URL onto the page + Then the image at that URL should be loaded for upload + And I should be taken to the upload progress view + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 64-66, 219-220 + # Evidence: kahuna/public/js/upload/manager.js lines 86-96 + + @todo + Scenario: Dropping a Witness contribution imports it + When I drop a Witness contribution URL onto the page + Then the importing overlay should be shown + And the Witness image should be imported with its metadata and usage rights + And I should be taken to the imported image's page + # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 8-12 + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 40-62, 69-78, 206-217 + + @todo + Scenario: A failed Witness import is reported + Given I drop a Witness contribution URL onto the page + When the Witness import fails + Then I should see an alert that importing the Witness contribution failed + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 79-85 + + @todo + Scenario: Dropping invalid content is rejected + When I drop something that is not a valid file or URL + Then I should see an alert that I must drop valid files or URLs + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 222-231 + + @todo + Scenario: Dragging a Grid image back onto the page is ignored + When I drag an image that is already in the Grid over the page + Then the dropzone overlay should not appear + # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 135-150, 158-160 + + # --------------------------------------------------------------------------- + # Current uploads list (jobs/upload-jobs.html + upload-jobs.js) + # --------------------------------------------------------------------------- + + @todo + Scenario: Current uploads show how many remain + Given I have several uploads in progress + When I view my current uploads + Then I should see a count of how many uploads remain + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 2 + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 45, 79, 88 + + @todo + Scenario: An uploading job shows a preview with its name and size + Given a file is uploading + When I view my current uploads + Then I should see a preview thumbnail with the file name and size + And I should see the job's status + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 9-31 + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 61-72 + # Evidence: kahuna/public/js/upload/manager.js lines 16-21 + + @todo + Scenario: A failed upload shows the error and can be removed + Given an upload has failed + When I view my current uploads + Then the job should be marked as an upload error with the error message + And I should be able to remove the failed job after confirming + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 24-38 + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 77-78, 179-185 + + @todo + Scenario: An unsupported file type gives a helpful error + When I upload a file that is not a JPG, PNG or TIFF + Then the job should show an error explaining only JPG, PNG and TIFF are supported + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 155-163 + + @todo + Scenario: A completed upload becomes an editable image + Given an upload has completed + When I view my current uploads + Then the job should switch to the image metadata editor + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 40-48 + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 84-92 + + @todo + Scenario: A missing description defaults to the file name + Given an uploaded image has no description + When the upload completes + Then the description should default to the file name without its extension + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 105-110 + + @todo + Scenario: Deleting an image removes it from current uploads + Given an uploaded image is shown in my current uploads + When the image is deleted + Then it should be removed from my current uploads + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 + + @todo + Scenario: Uploading a previously deleted image displays it for undeletion + Given an image is uploaded + And then deleted + And the same image is uploaded again + And I have delete permission + Then it should be present in my current uploads + And I should be able to undelete it + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 + + # Existing bug: the UI still presents this button even if permission to delete is not present + @todo + Scenario: Uploading a previously deleted image uploaded by a different user does not display undeletion button without the appropriate permission + Given an image is uploaded by a user that is not me + And then deleted + And the same image is uploaded again by me + And I do not have delete permissions + Then it should be present in current uploads + And I should see the undelete button + And the undelete button is disabled + And there should be a message indicating that I do not have permission to undelete + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 + + @todo + Scenario: A failed deletion is reported + Given an uploaded image is shown in my current uploads + When deleting the image fails + Then I should see an alert explaining the deletion failed + # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 197-203 + + # --------------------------------------------------------------------------- + # Required metadata editor (jobs/required-metadata-editor.html + .js) + # --------------------------------------------------------------------------- + + @todo + Scenario: Editing required metadata for an uploaded image + Given an uploaded image is shown in the metadata editor + When I fill in the description, byline and credit + Then the metadata should be saved automatically + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 1, 30-56, 61-72, 85-104 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 33, 40-65 + + @todo + Scenario: Description and credit are required + Given an uploaded image is shown in the metadata editor + When I leave the description or credit empty + Then those fields should be marked as required + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 36, 96 + + @todo + Scenario: The description placeholder gives guidance + Given an uploaded image with no description + When I view the description field + Then I should see placeholder guidance about who, what, where, when and why + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 34-40 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 157-161 + + # How to verify writes? + @todo + Scenario: Choosing an image type when image types are configured + Given image types are configured + When I view the metadata editor + Then I should be able to choose an image type from a dropdown + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 5-27 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 38 + + @todo + Scenario: The credit field suggests existing values + Given an uploaded image is shown in the metadata editor + When I type into the credit field + Then I should see suggestions matching existing credits + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 87-104 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 67-71 + + # How to verify writes? + @todo + Scenario: Metadata only shows when it was already present for some fields + Given an uploaded image that already has metadata values for the following fields: + | byline | + | credit | + | copyright | + | specialInstructions | + | description | + | domainMetadata | + | usageInstructions | + | imageType | + When I view the metadata editor + Then I should see the metadata values in the appropriate fields + And I should be able to edit those fields with other values + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 114-134 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 36 + + @todo + Scenario: Applying a field value to all current uploads in a batch + Given I am uploading more than one image + And I am permitted to edit + When I apply the following field values to all current uploads: + | Leases | + | Image type | + | Description | + | Byline | + | Credit | + | Special instructions | + | Collections | + | Labels | + | Keywords | + | Photoshoot | + Then that value should be applied to the same field on every current upload + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 20-27, 49-53, 76-80, 107-111, 129-133, 156-160, 179-183 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 108-119 + + @todo + Scenario: Metadata editing is disabled without edit permission + Given I am not permitted to edit the image + When I view the metadata editor for an image I did not upload + Then the metadata fields should be disabled + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 14, 44, 71, 101, 124, 152, 173 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 27-29 + + # This requires configuration, which is already present in CODE + @todo + Scenario: Applying a metadata template makes fields read-only + Given an uploaded image is shown in the metadata editor + When a metadata template is selected + Then the affected fields should be populated and made read-only + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 15, 45, 72, 102, 125, 153, 175 + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 84-98 + + @todo + Scenario: Removing a metadata template restores previously edited fields + Given an uploaded image is shown in the metadata editor + And a field is edited + When a metadata template is selected and then removed + Then the previously overridden fields are restored + + # --------------------------------------------------------------------------- + # Recent uploads (recent/recent-uploads.html + recent-uploads.js) + # --------------------------------------------------------------------------- + + @todo + Scenario: Past uploads are loading + When my past uploads have not yet loaded + Then I should see a loading message + # Evidence: kahuna/public/js/upload/recent/recent-uploads.html lines 1-2 + # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 34 + + @todo + Scenario: I have not uploaded anything yet + Given I have never uploaded an image + When my past uploads load + Then I should see a message that I haven't uploaded anything yet + # Evidence: kahuna/public/js/upload/recent/recent-uploads.html lines 4-5 + # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 25-34 + + @todo + Scenario: My past uploads are listed + Given I have uploaded images before + When my past uploads load + Then I should see each of my past uploaded images + And I should be able to delete an image I am permitted to delete + # Evidence: kahuna/public/js/upload/recent/recent-uploads.html lines 7-13 + # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 25-51 + + @todo + Scenario: A failed deletion of a past upload is reported + Given a past uploaded image is listed + When deleting the image fails + Then I should see an alert explaining the deletion failed + # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 63-69 From f6b1db7affa23a068633361cb248a408575e196f Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Thu, 10 Sep 2026 10:39:50 +0000 Subject: [PATCH 02/18] Tag unimplemented upload scenarios @todo and skip them The upload feature file specifies far more than we are wiring up in the first pass. Tag everything we are not implementing yet with @todo and exclude those tags from bddgen, so its "undefined step" errors stay meaningful for the scenarios we do implement. Gherkin tags cannot contain whitespace, so the pre-existing @todo annotations were failing to parse; their notes move to comments. Uploads mutate the single shared e2e user's upload history, so the feature runs @mode:serial. --- e2e-tests/features/upload.feature | 23 +- e2e-tests/features/upload.feature.pending | 415 ---------------------- e2e-tests/playwright.config.ts | 3 + 3 files changed, 18 insertions(+), 423 deletions(-) delete mode 100644 e2e-tests/features/upload.feature.pending diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index 4d2707b012..9d3c2ad112 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -78,9 +78,8 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 21 - @todo Scenario: Preset labels are applied to all uploads - When I add a preset label in the prompt + When I add a preset label via the 'apply label to all uploads' button Then that label should be applied to all my uploads # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 43, 113-114 @@ -150,7 +149,7 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 64-66, 219-220 # Evidence: kahuna/public/js/upload/manager.js lines 86-96 - @todo + @todo can remove this functionality in another PR Scenario: Dropping a Witness contribution imports it When I drop a Witness contribution URL onto the page Then the importing overlay should be shown @@ -159,14 +158,14 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 8-12 # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 40-62, 69-78, 206-217 - @todo + @todo can remove this functionality in another PR Scenario: A failed Witness import is reported Given I drop a Witness contribution URL onto the page When the Witness import fails Then I should see an alert that importing the Witness contribution failed # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 79-85 - @todo + @todo not implemented afaics Scenario: Dropping invalid content is rejected When I drop something that is not a valid file or URL Then I should see an alert that I must drop valid files or URLs @@ -233,14 +232,14 @@ Feature: Uploading images to the Grid @todo Scenario: Deleting an image removes it from current uploads Given an uploaded image is shown in my current uploads - When the image is deleted + When the image is deleted using the delete button at the bottom of the job form Then it should be removed from my current uploads # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 @todo Scenario: Uploading a previously deleted image displays it for undeletion Given an image is uploaded - And then deleted + And then deleted using the delete button at the bottom of the job form And the same image is uploaded again And I have delete permission Then it should be present in my current uploads @@ -325,10 +324,18 @@ Feature: Uploading images to the Grid | imageType | When I view the metadata editor Then I should see the metadata values in the appropriate fields - And I should be able to edit those fields with other values # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 114-134 # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 36 + # This also applies to any textual fields — we should pull them from img meta + @todo + Scenario: Existing usage instructions are shown with room for more + Given an uploaded image that already has usage instructions + When I view the metadata editor + Then I should see the existing usage instructions + And I should be able to add further special instructions + # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 140-167 + @todo Scenario: Applying a field value to all current uploads in a batch Given I am uploading more than one image diff --git a/e2e-tests/features/upload.feature.pending b/e2e-tests/features/upload.feature.pending deleted file mode 100644 index 9d3c2ad112..0000000000 --- a/e2e-tests/features/upload.feature.pending +++ /dev/null @@ -1,415 +0,0 @@ -# Uploads mutate the single shared e2e user's upload history, so scenarios must not race. -@mode:serial -Feature: Uploading images to the Grid - This lets an authorised user get images into the Grid by selecting files, - dragging and dropping files or URLs, importing Witness contributions, and - then completing the required metadata while tracking each upload's progress. - - Background: - Given the application stack is running - And I have opened the image upload page - - # --------------------------------------------------------------------------- - # Upload page shell (view.html + controller.js) - # --------------------------------------------------------------------------- - - Scenario: An authorised user sees the upload tools - Given I am permitted to upload images - When the upload page loads - Then I should see the file upload prompt - And I should see my past 50 uploads - And the drag-and-drop uploader should be active - # Evidence: kahuna/public/js/upload/view.html lines 11-12, 19-27, 31 - # Evidence: kahuna/public/js/upload/controller.js lines 40-45 - - # Needs a stack booted with enforceUploadImagesPermission=true. - @todo - Scenario: An unauthorised user is told they cannot upload - Given I am not permitted to upload images - When the upload page loads - Then I should see a message that I am not authorised to upload images - And I should see a link to email support - And I should not see the file upload prompt - And I should not see the drag-and-drop uploader - # Evidence: kahuna/public/js/upload/view.html lines 11, 29 - # Evidence: kahuna/public/js/upload/controller.js lines 36-41 - - Scenario: Returning to search from the upload page - When I choose "Back to search" from the top bar - Then I should be taken to the image search page - And my previous search should be intact - # Evidence: kahuna/public/js/upload/view.html lines 3-6 - - Scenario: The current uploads section only appears while an upload is running - Given I have an upload in progress - When the upload page loads - Then I should see my current uploads section - # Evidence: kahuna/public/js/upload/view.html lines 14-17 - # Evidence: kahuna/public/js/upload/controller.js lines 45 - - Scenario: Viewing all of my uploads - When I choose "View all your uploads" - Then I should be taken to a search filtered to images I uploaded - # Evidence: kahuna/public/js/upload/view.html lines 22-23 - - Scenario: Warning before leaving the page with uploads in progress - Given I have an upload in progress - When I try to navigate away from the upload page via the following buttons: - | Back to search | - | Home | - | View all your uploads | - Then I should be warned that uploads are in progress and asked to confirm - # Evidence: kahuna/public/js/upload/controller.js lines 18-34, 47-54 - - # --------------------------------------------------------------------------- - # File upload prompt (prompt/prompt.html + prompt.js) - # --------------------------------------------------------------------------- - - Scenario: The prompt explains how to upload - When the upload page loads - Then I should see a message telling me to drag and drop or click to upload to the system - # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 1-3 - # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 20 - - Scenario: The prompt suggests an example label when no labels are applied - Given I have not applied any preset labels - When the upload page loads - Then I should see a suggested example label to apply to all uploads - # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 - # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 21 - - Scenario: Preset labels are applied to all uploads - When I add a preset label via the 'apply label to all uploads' button - Then that label should be applied to all my uploads - # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 43, 113-114 - - @todo - Scenario: Preset labels are stored in local storage - When I add preset label(s) in the prompt - Then those label(s) should already be selected when I reload the page - - # --------------------------------------------------------------------------- - # Select-files uploader (file-uploader.html + file-uploader.js) - # --------------------------------------------------------------------------- - - Scenario: Opening the file picker from the Upload button - When I click the "Upload" button - Then the system file picker should open - # Evidence: kahuna/public/js/upload/file-uploader.html lines 9-15 - # Evidence: kahuna/public/js/upload/file-uploader.js lines 41-42 - - Scenario: Selecting files queues them for upload - When I select one or more image files to upload - Then those files should be queued for upload - And I should be taken to the upload progress view - # Evidence: kahuna/public/js/upload/file-uploader.html lines 4-7 - # Evidence: kahuna/public/js/upload/file-uploader.js lines 20-26 - # Evidence: kahuna/public/js/upload/manager.js lines 68-83 - - Scenario: Files above the size limit are skipped with a warning - Given an upload size limit is configured - When I select a file that is larger than the size limit - Then I should be warned that the oversized file will be skipped - And only the files within the limit should be queued for upload - # Evidence: kahuna/public/js/upload/manager.js lines 36-47 - - # --------------------------------------------------------------------------- - # Drag-and-drop uploader (dnd-uploader.html + dnd-uploader.js) - # --------------------------------------------------------------------------- - - @todo - Scenario: Dragging valid content over the page shows the dropzone - When I drag files over the upload page - Then the dropzone overlay should appear with an explanation - # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 1-6 - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 113, 116, 158-171 - - @todo - Scenario: The dropzone hides when I stop dragging - Given the dropzone overlay is showing - When I drag away from the upload page - Then the dropzone overlay should disappear - # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 1 - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 117, 173-180 - - @todo - Scenario: Dropping files uploads them - When I drop one or more image files onto the page - Then those files should be queued for upload - And I should be taken to the upload progress view - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 33-37, 182-205 - # Evidence: kahuna/public/js/upload/manager.js lines 68-83 - - @todo - Scenario: Dropping a URL loads the image from that URL - When I drop an image URL onto the page - Then the image at that URL should be loaded for upload - And I should be taken to the upload progress view - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 64-66, 219-220 - # Evidence: kahuna/public/js/upload/manager.js lines 86-96 - - @todo can remove this functionality in another PR - Scenario: Dropping a Witness contribution imports it - When I drop a Witness contribution URL onto the page - Then the importing overlay should be shown - And the Witness image should be imported with its metadata and usage rights - And I should be taken to the imported image's page - # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 8-12 - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 40-62, 69-78, 206-217 - - @todo can remove this functionality in another PR - Scenario: A failed Witness import is reported - Given I drop a Witness contribution URL onto the page - When the Witness import fails - Then I should see an alert that importing the Witness contribution failed - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 79-85 - - @todo not implemented afaics - Scenario: Dropping invalid content is rejected - When I drop something that is not a valid file or URL - Then I should see an alert that I must drop valid files or URLs - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 222-231 - - @todo - Scenario: Dragging a Grid image back onto the page is ignored - When I drag an image that is already in the Grid over the page - Then the dropzone overlay should not appear - # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 135-150, 158-160 - - # --------------------------------------------------------------------------- - # Current uploads list (jobs/upload-jobs.html + upload-jobs.js) - # --------------------------------------------------------------------------- - - @todo - Scenario: Current uploads show how many remain - Given I have several uploads in progress - When I view my current uploads - Then I should see a count of how many uploads remain - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 2 - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 45, 79, 88 - - @todo - Scenario: An uploading job shows a preview with its name and size - Given a file is uploading - When I view my current uploads - Then I should see a preview thumbnail with the file name and size - And I should see the job's status - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 9-31 - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 61-72 - # Evidence: kahuna/public/js/upload/manager.js lines 16-21 - - @todo - Scenario: A failed upload shows the error and can be removed - Given an upload has failed - When I view my current uploads - Then the job should be marked as an upload error with the error message - And I should be able to remove the failed job after confirming - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 24-38 - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 77-78, 179-185 - - @todo - Scenario: An unsupported file type gives a helpful error - When I upload a file that is not a JPG, PNG or TIFF - Then the job should show an error explaining only JPG, PNG and TIFF are supported - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 155-163 - - @todo - Scenario: A completed upload becomes an editable image - Given an upload has completed - When I view my current uploads - Then the job should switch to the image metadata editor - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.html lines 40-48 - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 84-92 - - @todo - Scenario: A missing description defaults to the file name - Given an uploaded image has no description - When the upload completes - Then the description should default to the file name without its extension - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 105-110 - - @todo - Scenario: Deleting an image removes it from current uploads - Given an uploaded image is shown in my current uploads - When the image is deleted using the delete button at the bottom of the job form - Then it should be removed from my current uploads - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 - - @todo - Scenario: Uploading a previously deleted image displays it for undeletion - Given an image is uploaded - And then deleted using the delete button at the bottom of the job form - And the same image is uploaded again - And I have delete permission - Then it should be present in my current uploads - And I should be able to undelete it - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 - - # Existing bug: the UI still presents this button even if permission to delete is not present - @todo - Scenario: Uploading a previously deleted image uploaded by a different user does not display undeletion button without the appropriate permission - Given an image is uploaded by a user that is not me - And then deleted - And the same image is uploaded again by me - And I do not have delete permissions - Then it should be present in current uploads - And I should see the undelete button - And the undelete button is disabled - And there should be a message indicating that I do not have permission to undelete - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 187-195 - - @todo - Scenario: A failed deletion is reported - Given an uploaded image is shown in my current uploads - When deleting the image fails - Then I should see an alert explaining the deletion failed - # Evidence: kahuna/public/js/upload/jobs/upload-jobs.js lines 197-203 - - # --------------------------------------------------------------------------- - # Required metadata editor (jobs/required-metadata-editor.html + .js) - # --------------------------------------------------------------------------- - - @todo - Scenario: Editing required metadata for an uploaded image - Given an uploaded image is shown in the metadata editor - When I fill in the description, byline and credit - Then the metadata should be saved automatically - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 1, 30-56, 61-72, 85-104 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 33, 40-65 - - @todo - Scenario: Description and credit are required - Given an uploaded image is shown in the metadata editor - When I leave the description or credit empty - Then those fields should be marked as required - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 36, 96 - - @todo - Scenario: The description placeholder gives guidance - Given an uploaded image with no description - When I view the description field - Then I should see placeholder guidance about who, what, where, when and why - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 34-40 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 157-161 - - # How to verify writes? - @todo - Scenario: Choosing an image type when image types are configured - Given image types are configured - When I view the metadata editor - Then I should be able to choose an image type from a dropdown - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 5-27 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 38 - - @todo - Scenario: The credit field suggests existing values - Given an uploaded image is shown in the metadata editor - When I type into the credit field - Then I should see suggestions matching existing credits - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 87-104 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 67-71 - - # How to verify writes? - @todo - Scenario: Metadata only shows when it was already present for some fields - Given an uploaded image that already has metadata values for the following fields: - | byline | - | credit | - | copyright | - | specialInstructions | - | description | - | domainMetadata | - | usageInstructions | - | imageType | - When I view the metadata editor - Then I should see the metadata values in the appropriate fields - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 114-134 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 36 - - # This also applies to any textual fields — we should pull them from img meta - @todo - Scenario: Existing usage instructions are shown with room for more - Given an uploaded image that already has usage instructions - When I view the metadata editor - Then I should see the existing usage instructions - And I should be able to add further special instructions - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 140-167 - - @todo - Scenario: Applying a field value to all current uploads in a batch - Given I am uploading more than one image - And I am permitted to edit - When I apply the following field values to all current uploads: - | Leases | - | Image type | - | Description | - | Byline | - | Credit | - | Special instructions | - | Collections | - | Labels | - | Keywords | - | Photoshoot | - Then that value should be applied to the same field on every current upload - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 20-27, 49-53, 76-80, 107-111, 129-133, 156-160, 179-183 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 108-119 - - @todo - Scenario: Metadata editing is disabled without edit permission - Given I am not permitted to edit the image - When I view the metadata editor for an image I did not upload - Then the metadata fields should be disabled - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 14, 44, 71, 101, 124, 152, 173 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 27-29 - - # This requires configuration, which is already present in CODE - @todo - Scenario: Applying a metadata template makes fields read-only - Given an uploaded image is shown in the metadata editor - When a metadata template is selected - Then the affected fields should be populated and made read-only - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 15, 45, 72, 102, 125, 153, 175 - # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.js lines 84-98 - - @todo - Scenario: Removing a metadata template restores previously edited fields - Given an uploaded image is shown in the metadata editor - And a field is edited - When a metadata template is selected and then removed - Then the previously overridden fields are restored - - # --------------------------------------------------------------------------- - # Recent uploads (recent/recent-uploads.html + recent-uploads.js) - # --------------------------------------------------------------------------- - - @todo - Scenario: Past uploads are loading - When my past uploads have not yet loaded - Then I should see a loading message - # Evidence: kahuna/public/js/upload/recent/recent-uploads.html lines 1-2 - # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 34 - - @todo - Scenario: I have not uploaded anything yet - Given I have never uploaded an image - When my past uploads load - Then I should see a message that I haven't uploaded anything yet - # Evidence: kahuna/public/js/upload/recent/recent-uploads.html lines 4-5 - # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 25-34 - - @todo - Scenario: My past uploads are listed - Given I have uploaded images before - When my past uploads load - Then I should see each of my past uploaded images - And I should be able to delete an image I am permitted to delete - # Evidence: kahuna/public/js/upload/recent/recent-uploads.html lines 7-13 - # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 25-51 - - @todo - Scenario: A failed deletion of a past upload is reported - Given a past uploaded image is listed - When deleting the image fails - Then I should see an alert explaining the deletion failed - # Evidence: kahuna/public/js/upload/recent/recent-uploads.js lines 63-69 diff --git a/e2e-tests/playwright.config.ts b/e2e-tests/playwright.config.ts index 15f9ca94c5..b2506c2ed0 100644 --- a/e2e-tests/playwright.config.ts +++ b/e2e-tests/playwright.config.ts @@ -7,6 +7,9 @@ import { KAHUNA_PORT } from './setup/constants.ts'; const testDir = defineBddConfig({ features: './features/**/*.feature', steps: './steps/**/*.ts', + /* @todo marks scenarios that are specified but not yet implemented. Excluding them keeps + bddgen's "undefined step" failures meaningful for the scenarios we do implement. */ + tags: 'not @todo', }); /** From 6520661d878a46c7e49bf5e239f5b5b8a2324694 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Thu, 10 Sep 2026 11:21:50 +0000 Subject: [PATCH 03/18] Implement the runnable upload e2e scenarios Wires up the upload page shell, prompt and select-files scenarios against the real stack: eight scenarios covering the upload tools rendering, navigation to search and to a filtered search, the current-uploads section, the prompt copy, the file picker, queueing selected files, and the upload size limit. Locators live in one place (steps/support/upload-page.ts) and prefer roles and accessible names. `` gains a region role so "the drag-and-drop uploader should be active" has a hook. Two things needed handling to make the page testable at all: * Kahuna's first-run graphic-image blur explainer covers the top bar, so the background accepts the default via its cookie. * An upload reaches a terminal state within about a second, so the scenario that needs one in flight holds the transfer to the ingest bucket open. Two more scenarios are tagged @todo because the app cannot currently satisfy them, with the reason recorded against each: the "uploads in progress" leave confirmation never fires (ui-router destroys the controller scope before $locationChangeStart), and the prompt's example label is guarded by a `ctrl` that does not exist in filePrompt's isolated scope. --- e2e-tests/features/upload.feature | 6 + e2e-tests/fixtures/images/test-card-f.jpg | Bin 0 -> 51942 bytes e2e-tests/fixtures/images/test.jpg | Bin 0 -> 70488 bytes e2e-tests/steps/common.steps.ts | 28 +++++ e2e-tests/steps/fixtures.ts | 7 +- e2e-tests/steps/support/upload-page.ts | 50 ++++++++ e2e-tests/steps/upload.steps.ts | 141 ++++++++++++++++++++++ kahuna/public/js/upload/view.html | 2 +- 8 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 e2e-tests/fixtures/images/test-card-f.jpg create mode 100644 e2e-tests/fixtures/images/test.jpg create mode 100644 e2e-tests/steps/common.steps.ts create mode 100644 e2e-tests/steps/support/upload-page.ts create mode 100644 e2e-tests/steps/upload.steps.ts diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index 9d3c2ad112..e87e4b9d14 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -52,6 +52,9 @@ Feature: Uploading images to the Grid Then I should be taken to a search filtered to images I uploaded # Evidence: kahuna/public/js/upload/view.html lines 22-23 + # Currently unreachable: ui-router destroys the UploadCtrl scope before broadcasting + # $locationChangeStart, so the controller's warning listener never runs on a ui-sref click. + @todo Scenario: Warning before leaving the page with uploads in progress Given I have an upload in progress When I try to navigate away from the upload page via the following buttons: @@ -71,6 +74,9 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 1-3 # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 20 + # Currently unreachable: prompt.html guards the example label with `ctrl.presetLabels`, + # but filePrompt has an isolated scope and no controller, so `ctrl` is always undefined. + @todo Scenario: The prompt suggests an example label when no labels are applied Given I have not applied any preset labels When the upload page loads diff --git a/e2e-tests/fixtures/images/test-card-f.jpg b/e2e-tests/fixtures/images/test-card-f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a57b764b234eddb94913aaef880a4b4e912e77ec GIT binary patch literal 51942 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 Xv { + const response = await request.get('/management/healthcheck'); + expect(response.ok()).toBeTruthy(); +}); + +Given('I have opened the image upload page', async ({ page, testContext }) => { + // An unhandled dialog blocks the page, so record every one and answer it. Dismissing a + // confirm answers "no", which keeps us on the page under test. + page.on('dialog', (dialog) => { + testContext.dialogs.push(dialog.message()); + void dialog.dismiss(); + }); + + // Accept the default of blurring graphic images up front. Kahuna otherwise shows a + // first-run explainer overlay that covers the top bar (see services/graphic-image-blur.js). + await page.context().addCookies([ + { + name: 'SHOULD_BLUR_GRAPHIC_IMAGES', + value: 'true', + domain: `.${new URL(KAHUNA_APP_URL).hostname}`, + path: '/', + }, + ]); + + await page.goto(`${KAHUNA_APP_URL}/upload`); +}); diff --git a/e2e-tests/steps/fixtures.ts b/e2e-tests/steps/fixtures.ts index 081db695d8..054a25c865 100644 --- a/e2e-tests/steps/fixtures.ts +++ b/e2e-tests/steps/fixtures.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { APIResponse, Response } from '@playwright/test'; +import type { APIResponse, FileChooser, Response } from '@playwright/test'; import { test as base, createBdd } from 'playwright-bdd'; import { DOMAIN } from '../setup/constants.ts'; @@ -12,6 +12,9 @@ export const KAHUNA_APP_URL = `https://media.${DOMAIN}`; interface TestContext { response?: APIResponse; mediaApiResponse?: Promise; + /** Messages from alerts and confirms, in the order the page raised them. */ + dialogs: string[]; + fileChooser?: Promise; } /** @@ -21,7 +24,7 @@ interface TestContext { */ export const test = base.extend<{ testContext: TestContext }>({ testContext: async ({}, use) => { - await use({}); + await use({ dialogs: [] }); }, }); diff --git a/e2e-tests/steps/support/upload-page.ts b/e2e-tests/steps/support/upload-page.ts new file mode 100644 index 0000000000..8ce5686b1b --- /dev/null +++ b/e2e-tests/steps/support/upload-page.ts @@ -0,0 +1,50 @@ +import { statSync } from 'node:fs'; +import * as path from 'node:path'; +import type { Page } from '@playwright/test'; + +declare global { + interface Window { + _clientConfig: { + systemName: string; + maybeUploadLimitInBytes: number; + }; + } +} + +const FIXTURE_IMAGES = path.join(import.meta.dirname, '..', '..', 'fixtures', 'images'); + +export interface TestImage { + fileName: string; + path: string; + bytes: number; +} + +const testImage = (fileName: string): TestImage => { + const filePath = path.join(FIXTURE_IMAGES, fileName); + return { fileName, path: filePath, bytes: statSync(filePath).size }; +}; + +/** Sizes are read from disk so the size-limit scenario can pick a threshold between them. */ +export const testImages = { + smaller: testImage('test-card-f.jpg'), + larger: testImage('test.jpg'), +}; + +export const uploadPage = (page: Page) => { + const prompt = page.getByRole('region', { name: 'File upload' }); + + return { + prompt, + main: page.getByRole('main', { name: 'Image uploads' }), + currentUploads: page.getByRole('region', { name: 'Your current uploads' }), + pastUploads: page.getByRole('region', { name: 'Your past 50 uploads' }), + dragAndDropUploader: page.getByRole('region', { name: 'Drag and drop uploader' }), + fileInput: prompt.locator('input[name="files"]'), + /* The upload and back-to-search controls carry aria-labels that override their visible + text, so filter on the text the feature file names rather than the accessible name. */ + uploadButton: (label: string) => prompt.getByRole('button').filter({ hasText: label }), + topBarLink: (label: string) => page.getByRole('banner').getByRole('link').filter({ hasText: label }), + /** A queued or in-flight upload, before it becomes an editable image. */ + job: (fileName: string) => page.getByRole('region', { name: `${fileName} upload` }), + }; +}; diff --git a/e2e-tests/steps/upload.steps.ts b/e2e-tests/steps/upload.steps.ts new file mode 100644 index 0000000000..bf433536c6 --- /dev/null +++ b/e2e-tests/steps/upload.steps.ts @@ -0,0 +1,141 @@ +import { Given, KAHUNA_APP_URL, Then, When, expect } from './fixtures.ts'; +import { testImages, uploadPage } from './support/upload-page.ts'; + +const FILES_TO_UPLOAD = [testImages.smaller, testImages.larger]; + +/** Long enough for a scenario's assertions, short enough not to drag out teardown. */ +const UPLOAD_HOLD_MS = 5_000; + +// --------------------------------------------------------------------------- +// Upload page shell +// --------------------------------------------------------------------------- + +Given('I am permitted to upload images', async ({ page }) => { + // Kahuna decides whether to show the upload tools by looking for a `loader` link on the + // media API root, which the API only emits for users holding the upload permission. + const mediaApiUri = await page.evaluate( + () => document.querySelector('link[rel="media-api-uri"]')?.getAttribute('href'), + ); + const response = await page.request.get(mediaApiUri!); + const { links } = (await response.json()) as { links: { rel: string }[] }; + + expect(links.map((link) => link.rel)).toContain('loader'); +}); + +When('the upload page loads', async ({ page }) => { + await expect(uploadPage(page).main).toBeVisible(); +}); + +Then('I should see the file upload prompt', async ({ page }) => { + await expect(uploadPage(page).prompt).toBeVisible(); +}); + +Then('I should see my past {int} uploads', async ({ page }, count: number) => { + await expect(page.getByRole('region', { name: `Your past ${count} uploads` })).toBeVisible(); +}); + +Then('the drag-and-drop uploader should be active', async ({ page }) => { + // The dropzone overlay only renders mid-drag, so "active" means mounted and listening. + await expect(uploadPage(page).dragAndDropUploader).toBeAttached(); +}); + +When('I choose {string} from the top bar', async ({ page }, label: string) => { + await uploadPage(page).topBarLink(label).click(); +}); + +Then('I should be taken to the image search page', async ({ page }) => { + await expect(page).toHaveURL((url) => url.origin === KAHUNA_APP_URL && url.pathname === '/search'); +}); + +Given('I have an upload in progress', async ({ page }) => { + // Hold the transfer to the ingest bucket open, otherwise the job reaches a terminal + // state within a second or so and is no longer "in progress" by the time we assert. + await page.route( + (url) => url.hostname.startsWith('localstack.'), + async (route) => { + if (route.request().method() !== 'PUT') return route.fallback(); + await new Promise((resolve) => setTimeout(resolve, UPLOAD_HOLD_MS)); + await route.abort(); + }, + ); + + await uploadPage(page).fileInput.setInputFiles(testImages.smaller.path); + await expect(uploadPage(page).job(testImages.smaller.fileName)).toBeVisible(); +}); + +Then('I should see my current uploads section', async ({ page }) => { + await expect(uploadPage(page).currentUploads).toBeVisible(); +}); + +When('I choose {string}', async ({ page }, label: string) => { + await uploadPage(page).pastUploads.getByRole('link', { name: label }).click(); +}); + +Then('I should be taken to a search filtered to images I uploaded', async ({ page }) => { + await expect(page).toHaveURL((url) => Boolean(url.searchParams.get('uploadedBy'))); +}); + +// --------------------------------------------------------------------------- +// File upload prompt +// --------------------------------------------------------------------------- + +Then('I should see a message telling me to drag and drop or click to upload to the system', async ({ page }) => { + const systemName = await page.evaluate(() => window._clientConfig.systemName); + + await expect(uploadPage(page).prompt).toContainText( + `Either drag 'n drop images onto this screen or click`, + ); + await expect(uploadPage(page).prompt).toContainText(`to get your images on ${systemName}.`); +}); + +// --------------------------------------------------------------------------- +// Select-files uploader +// --------------------------------------------------------------------------- + +When('I click the {string} button', async ({ page, testContext }, label: string) => { + testContext.fileChooser = page.waitForEvent('filechooser'); + await uploadPage(page).uploadButton(label).click(); +}); + +Then('the system file picker should open', async ({ testContext }) => { + const fileChooser = await testContext.fileChooser; + + expect(fileChooser?.isMultiple()).toBe(true); +}); + +When('I select one or more image files to upload', async ({ page }) => { + await uploadPage(page).fileInput.setInputFiles(FILES_TO_UPLOAD.map(({ path }) => path)); +}); + +Then('those files should be queued for upload', async ({ page }) => { + for (const { fileName } of FILES_TO_UPLOAD) { + await expect(uploadPage(page).job(fileName)).toBeVisible(); + } +}); + +Then('I should be taken to the upload progress view', async ({ page }) => { + await expect(uploadPage(page).currentUploads).toBeVisible(); +}); + +Given('an upload size limit is configured', async ({ page }) => { + // upload/manager.js reads the limit when files are selected, so setting it now is enough. + const limit = Math.round((testImages.smaller.bytes + testImages.larger.bytes) / 2); + + await page.evaluate((bytes) => { + window._clientConfig.maybeUploadLimitInBytes = bytes; + }, limit); +}); + +When('I select a file that is larger than the size limit', async ({ page }) => { + await uploadPage(page).fileInput.setInputFiles(FILES_TO_UPLOAD.map(({ path }) => path)); +}); + +Then('I should be warned that the oversized file will be skipped', async ({ testContext }) => { + await expect.poll(() => testContext.dialogs.join('\n')).toContain('are above the size limit'); + expect(testContext.dialogs.join('\n')).toContain(testImages.larger.fileName); +}); + +Then('only the files within the limit should be queued for upload', async ({ page }) => { + await expect(uploadPage(page).job(testImages.smaller.fileName)).toBeVisible(); + await expect(uploadPage(page).job(testImages.larger.fileName)).toBeHidden(); +}); diff --git a/kahuna/public/js/upload/view.html b/kahuna/public/js/upload/view.html index db0798bf6b..dbe9cef750 100644 --- a/kahuna/public/js/upload/view.html +++ b/kahuna/public/js/upload/view.html @@ -28,4 +28,4 @@

Your past 50 uploads

- + From 7b371f0be3799fba91817cef0a2fe665513f5cb1 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Thu, 10 Sep 2026 12:15:06 +0000 Subject: [PATCH 04/18] Restore the uploads-in-progress leave warning scenario The warning does fire, but only for history navigations. On a ui-sref click ui-router destroys the UploadCtrl scope before broadcasting $locationChangeStart, so the listener registered on that scope never runs; pressing back reaches it. The step now goes back rather than clicking the top bar link. For back to have somewhere to return to within the same document, the background arrives at the upload page from search the way a user does, instead of deep-linking to /upload. --- e2e-tests/features/upload.feature | 3 --- e2e-tests/steps/common.steps.ts | 6 +++++- e2e-tests/steps/upload.steps.ts | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index e87e4b9d14..3ba0f25238 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -52,9 +52,6 @@ Feature: Uploading images to the Grid Then I should be taken to a search filtered to images I uploaded # Evidence: kahuna/public/js/upload/view.html lines 22-23 - # Currently unreachable: ui-router destroys the UploadCtrl scope before broadcasting - # $locationChangeStart, so the controller's warning listener never runs on a ui-sref click. - @todo Scenario: Warning before leaving the page with uploads in progress Given I have an upload in progress When I try to navigate away from the upload page via the following buttons: diff --git a/e2e-tests/steps/common.steps.ts b/e2e-tests/steps/common.steps.ts index 271f1711e3..4a664e28f3 100644 --- a/e2e-tests/steps/common.steps.ts +++ b/e2e-tests/steps/common.steps.ts @@ -24,5 +24,9 @@ Given('I have opened the image upload page', async ({ page, testContext }) => { }, ]); - await page.goto(`${KAHUNA_APP_URL}/upload`); + // Arrive from search rather than deep-linking, so the upload page has a same-document + // history entry behind it and back-navigation behaves as it does for a real user. + await page.goto(KAHUNA_APP_URL); + await page.getByRole('banner').getByRole('link', { name: 'My recent uploads' }).click(); + await page.waitForURL('**/upload'); }); diff --git a/e2e-tests/steps/upload.steps.ts b/e2e-tests/steps/upload.steps.ts index bf433536c6..2e2d169678 100644 --- a/e2e-tests/steps/upload.steps.ts +++ b/e2e-tests/steps/upload.steps.ts @@ -75,6 +75,19 @@ Then('I should be taken to a search filtered to images I uploaded', async ({ pag await expect(page).toHaveURL((url) => Boolean(url.searchParams.get('uploadedBy'))); }); +When('I try to navigate away from the upload page', async ({ page }) => { + // Going back is what a user does, and it is also the only navigation the controller's + // guard sees: on a ui-sref click ui-router destroys its scope before broadcasting + // $locationChangeStart, so the listener registered there never runs. + await page.goBack(); +}); + +Then('I should be warned that uploads are in progress and asked to confirm', async ({ testContext }) => { + await expect + .poll(() => testContext.dialogs) + .toContain('You have uploads in progress. Are you sure you want to leave this page?'); +}); + // --------------------------------------------------------------------------- // File upload prompt // --------------------------------------------------------------------------- From 8b5af0b6ef48bc5b76baa1af2568a7f57b7d7a0c Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Thu, 10 Sep 2026 12:28:03 +0000 Subject: [PATCH 05/18] Show the upload prompt's example label when no labels are applied prompt.html guarded the hint with `ctrl.presetLabels` and `ctrl.active`, neither of which filePrompt provided. That name was not simply unset: the nested has no scope of its own, so its `controllerAs: 'ctrl'` publishes FileUploaderCtrl onto the prompt's scope, and the guard read properties off that. Expose the check on the prompt's own scope instead, sourced from presetLabelService so it follows labels being added and removed in gr-preset-labels' isolated scope. The `!active` part of the guard is dropped: while the add-label form is open its input already shows the same example as a placeholder. --- e2e-tests/features/upload.feature | 3 --- e2e-tests/steps/upload.steps.ts | 12 ++++++++++++ kahuna/public/js/upload/prompt/prompt.html | 2 +- kahuna/public/js/upload/prompt/prompt.js | 9 +++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index 3ba0f25238..9d3c2ad112 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -71,9 +71,6 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 1-3 # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 20 - # Currently unreachable: prompt.html guards the example label with `ctrl.presetLabels`, - # but filePrompt has an isolated scope and no controller, so `ctrl` is always undefined. - @todo Scenario: The prompt suggests an example label when no labels are applied Given I have not applied any preset labels When the upload page loads diff --git a/e2e-tests/steps/upload.steps.ts b/e2e-tests/steps/upload.steps.ts index 2e2d169678..85e0f11bfd 100644 --- a/e2e-tests/steps/upload.steps.ts +++ b/e2e-tests/steps/upload.steps.ts @@ -1,6 +1,9 @@ import { Given, KAHUNA_APP_URL, Then, When, expect } from './fixtures.ts'; import { testImages, uploadPage } from './support/upload-page.ts'; +/** Example label offered by the prompt, from kahuna/public/js/strings.json. */ +const EXAMPLE_LABEL = 'Observer'; + const FILES_TO_UPLOAD = [testImages.smaller, testImages.larger]; /** Long enough for a scenario's assertions, short enough not to drag out teardown. */ @@ -101,6 +104,15 @@ Then('I should see a message telling me to drag and drop or click to upload to t await expect(uploadPage(page).prompt).toContainText(`to get your images on ${systemName}.`); }); +Given('I have not applied any preset labels', async ({ page }) => { + await page.evaluate(() => window.localStorage.removeItem('preset-labels')); + await page.reload(); +}); + +Then('I should see a suggested example label to apply to all uploads', async ({ page }) => { + await expect(uploadPage(page).prompt).toContainText(`label e.g. ${EXAMPLE_LABEL}`); +}); + // --------------------------------------------------------------------------- // Select-files uploader // --------------------------------------------------------------------------- diff --git a/kahuna/public/js/upload/prompt/prompt.html b/kahuna/public/js/upload/prompt/prompt.html index 4ce1492b97..98475b5933 100644 --- a/kahuna/public/js/upload/prompt/prompt.html +++ b/kahuna/public/js/upload/prompt/prompt.html @@ -4,7 +4,7 @@

Apply - label e.g. {{exampleLabel}} + label e.g. {{exampleLabel}}

to all uploads

diff --git a/kahuna/public/js/upload/prompt/prompt.js b/kahuna/public/js/upload/prompt/prompt.js index 06f8fd7324..4022704a3f 100644 --- a/kahuna/public/js/upload/prompt/prompt.js +++ b/kahuna/public/js/upload/prompt/prompt.js @@ -1,16 +1,16 @@ import angular from 'angular'; import './prompt.css'; -import template from './prompt.html'; import '../../components/gr-preset-labels/gr-preset-labels'; - +import template from './prompt.html'; import strings from '../../strings.json'; export let prompt = angular.module('kahuna.upload.prompt', [ - 'gr.presetLabels' + 'gr.presetLabels', + 'kahuna.services.presetLabel' ]); -prompt.directive('filePrompt', [function () { +prompt.directive('filePrompt', ['presetLabelService', function (presetLabelService) { return { restrict: 'E', transclude: 'replace', @@ -19,6 +19,7 @@ prompt.directive('filePrompt', [function () { link: function($scope) { $scope.systemName = window._clientConfig.systemName; $scope.exampleLabel = strings.exampleLabel; + $scope.hasPresetLabels = () => presetLabelService.getLabels().length > 0; } }; }]); From 1eda37991453e7fc5818f524e4c7776f1295b3c8 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Mon, 14 Sep 2026 14:35:12 +0000 Subject: [PATCH 06/18] Minor scenario changes to accommodate testing requirements --- e2e-tests/features/upload.feature | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index 9d3c2ad112..fecb582fb4 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -35,6 +35,7 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/controller.js lines 36-41 Scenario: Returning to search from the upload page + Given I had searched for "cats" before opening the upload page When I choose "Back to search" from the top bar Then I should be taken to the image search page And my previous search should be intact @@ -52,6 +53,11 @@ Feature: Uploading images to the Grid Then I should be taken to a search filtered to images I uploaded # Evidence: kahuna/public/js/upload/view.html lines 22-23 + # Currently unreachable: none of these controls reach the controller's own confirm. + # "Back to search" is a ui-sref, and ui-router destroys the UploadCtrl scope before + # broadcasting $locationChangeStart; "Home" and "View all your uploads" are plain hrefs, + # so the browser shows its own generic beforeunload prompt instead. + @todo Scenario: Warning before leaving the page with uploads in progress Given I have an upload in progress When I try to navigate away from the upload page via the following buttons: From f361d4f375e03a8e544bc300af9e0689f878a81c Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Mon, 14 Sep 2026 14:35:35 +0000 Subject: [PATCH 07/18] Adapt tests to feature changes --- e2e-tests/steps/fixtures.ts | 2 ++ e2e-tests/steps/support/upload-page.ts | 2 ++ e2e-tests/steps/upload.steps.ts | 37 +++++++++++++++++++++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/e2e-tests/steps/fixtures.ts b/e2e-tests/steps/fixtures.ts index 054a25c865..15460542ce 100644 --- a/e2e-tests/steps/fixtures.ts +++ b/e2e-tests/steps/fixtures.ts @@ -15,6 +15,8 @@ interface TestContext { /** Messages from alerts and confirms, in the order the page raised them. */ dialogs: string[]; fileChooser?: Promise; + /** The query run on the search page before navigating to the upload page. */ + previousSearchQuery?: string; } /** diff --git a/e2e-tests/steps/support/upload-page.ts b/e2e-tests/steps/support/upload-page.ts index 8ce5686b1b..6875979350 100644 --- a/e2e-tests/steps/support/upload-page.ts +++ b/e2e-tests/steps/support/upload-page.ts @@ -44,6 +44,8 @@ export const uploadPage = (page: Page) => { text, so filter on the text the feature file names rather than the accessible name. */ uploadButton: (label: string) => prompt.getByRole('button').filter({ hasText: label }), topBarLink: (label: string) => page.getByRole('banner').getByRole('link').filter({ hasText: label }), + /** Any control that takes you off the upload page, wherever it sits on it. */ + leaveLink: (label: string) => page.getByRole('link').filter({ hasText: label }), /** A queued or in-flight upload, before it becomes an editable image. */ job: (fileName: string) => page.getByRole('region', { name: `${fileName} upload` }), }; diff --git a/e2e-tests/steps/upload.steps.ts b/e2e-tests/steps/upload.steps.ts index 85e0f11bfd..ca3754ac98 100644 --- a/e2e-tests/steps/upload.steps.ts +++ b/e2e-tests/steps/upload.steps.ts @@ -1,3 +1,4 @@ +import type { DataTable } from 'playwright-bdd'; import { Given, KAHUNA_APP_URL, Then, When, expect } from './fixtures.ts'; import { testImages, uploadPage } from './support/upload-page.ts'; @@ -42,6 +43,18 @@ Then('the drag-and-drop uploader should be active', async ({ page }) => { await expect(uploadPage(page).dragAndDropUploader).toBeAttached(); }); +Given( + 'I had searched for {string} before opening the upload page', + async ({ page, testContext }, query: string) => { + await page.goto(`${KAHUNA_APP_URL}/search?query=${encodeURIComponent(query)}`); + await expect(page.getByRole('main', { name: 'Image search results' })).toBeVisible(); + testContext.previousSearchQuery = query; + + await page.getByRole('banner').getByRole('link', { name: 'My recent uploads' }).click(); + await page.waitForURL('**/upload'); + }, +); + When('I choose {string} from the top bar', async ({ page }, label: string) => { await uploadPage(page).topBarLink(label).click(); }); @@ -50,6 +63,14 @@ Then('I should be taken to the image search page', async ({ page }) => { await expect(page).toHaveURL((url) => url.origin === KAHUNA_APP_URL && url.pathname === '/search'); }); +Then('my previous search should be intact', async ({ page, testContext }) => { + // The `search` state is a deep-state redirect, so entering it without params sends us + // back to the `search.results` params we left behind. + await expect(page).toHaveURL( + (url) => url.searchParams.get('query') === testContext.previousSearchQuery, + ); +}); + Given('I have an upload in progress', async ({ page }) => { // Hold the transfer to the ingest bucket open, otherwise the job reaches a terminal // state within a second or so and is no longer "in progress" by the time we assert. @@ -78,12 +99,16 @@ Then('I should be taken to a search filtered to images I uploaded', async ({ pag await expect(page).toHaveURL((url) => Boolean(url.searchParams.get('uploadedBy'))); }); -When('I try to navigate away from the upload page', async ({ page }) => { - // Going back is what a user does, and it is also the only navigation the controller's - // guard sees: on a ui-sref click ui-router destroys its scope before broadcasting - // $locationChangeStart, so the listener registered there never runs. - await page.goBack(); -}); +When( + 'I try to navigate away from the upload page via the following buttons:', + async ({ page }, buttons: DataTable) => { + for (const [label] of buttons.raw()) { + await uploadPage(page).leaveLink(label).click(); + // Dismissing the confirm answers "no", so each button leaves us here for the next one. + await expect(page).toHaveURL(/\/upload/); + } + }, +); Then('I should be warned that uploads are in progress and asked to confirm', async ({ testContext }) => { await expect From e1dd82d0e6a3a6865e78b738284db7bc4f6527ef Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 11:06:38 +0000 Subject: [PATCH 08/18] Move comments out of todo directives --- e2e-tests/features/upload.feature | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index fecb582fb4..b3bc089b71 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -84,6 +84,7 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 5-11 # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 21 + @todo Scenario: Preset labels are applied to all uploads When I add a preset label via the 'apply label to all uploads' button Then that label should be applied to all my uploads @@ -155,7 +156,8 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 64-66, 219-220 # Evidence: kahuna/public/js/upload/manager.js lines 86-96 - @todo can remove this functionality in another PR + # can remove this functionality in another PR + @todo Scenario: Dropping a Witness contribution imports it When I drop a Witness contribution URL onto the page Then the importing overlay should be shown @@ -164,14 +166,16 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/dnd-uploader.html lines 8-12 # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 40-62, 69-78, 206-217 - @todo can remove this functionality in another PR + # can remove this functionality in another PR + @todo Scenario: A failed Witness import is reported Given I drop a Witness contribution URL onto the page When the Witness import fails Then I should see an alert that importing the Witness contribution failed # Evidence: kahuna/public/js/upload/dnd-uploader.js lines 79-85 - @todo not implemented afaics + # not implemented afaics + @todo Scenario: Dropping invalid content is rejected When I drop something that is not a valid file or URL Then I should see an alert that I must drop valid files or URLs From e94a8773daa48f11205ed15687655f634962288e Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 11:28:08 +0000 Subject: [PATCH 09/18] Move uploads steps to own folder --- e2e-tests/steps/global-setup.ts | 15 --------------- .../{support/upload-page.ts => upload/setup.ts} | 0 e2e-tests/steps/{ => upload}/upload.steps.ts | 4 ++-- 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 e2e-tests/steps/global-setup.ts rename e2e-tests/steps/{support/upload-page.ts => upload/setup.ts} (100%) rename e2e-tests/steps/{ => upload}/upload.steps.ts (98%) diff --git a/e2e-tests/steps/global-setup.ts b/e2e-tests/steps/global-setup.ts deleted file mode 100644 index 7f3724875f..0000000000 --- a/e2e-tests/steps/global-setup.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Playwright global setup. Attaches to a running Grid stack if there is one, otherwise - * boots a fresh one (see `setup/stack.ts`), stashing it for `global-teardown.ts` - * and exposing the Kahuna base URL via `GRID_BASE_URL`. - */ -import { ensureStack } from '../setup/stack.ts'; -import { setEnvironment } from '../setup/state.ts'; - -async function globalSetup(): Promise { - const environment = await ensureStack(); - process.env.GRID_BASE_URL = environment.baseUrl; - setEnvironment(environment); -} - -export default globalSetup; diff --git a/e2e-tests/steps/support/upload-page.ts b/e2e-tests/steps/upload/setup.ts similarity index 100% rename from e2e-tests/steps/support/upload-page.ts rename to e2e-tests/steps/upload/setup.ts diff --git a/e2e-tests/steps/upload.steps.ts b/e2e-tests/steps/upload/upload.steps.ts similarity index 98% rename from e2e-tests/steps/upload.steps.ts rename to e2e-tests/steps/upload/upload.steps.ts index ca3754ac98..2a87ca289e 100644 --- a/e2e-tests/steps/upload.steps.ts +++ b/e2e-tests/steps/upload/upload.steps.ts @@ -1,6 +1,6 @@ import type { DataTable } from 'playwright-bdd'; -import { Given, KAHUNA_APP_URL, Then, When, expect } from './fixtures.ts'; -import { testImages, uploadPage } from './support/upload-page.ts'; +import { Given, KAHUNA_APP_URL, Then, When, expect } from '../fixtures.ts'; +import { testImages, uploadPage } from './setup.ts'; /** Example label offered by the prompt, from kahuna/public/js/strings.json. */ const EXAMPLE_LABEL = 'Observer'; From 208587b6868dc00f8221901f2faf35fd06696e99 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:09:33 +0000 Subject: [PATCH 10/18] Add detail to metadata w/o edito permission scenario --- e2e-tests/features/upload.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index b3bc089b71..299c08a941 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -367,7 +367,7 @@ Feature: Uploading images to the Grid @todo Scenario: Metadata editing is disabled without edit permission - Given I am not permitted to edit the image + Given I am not permitted to edit the image, as it has been uploaded by another user and I do not have edit_metadata permission When I view the metadata editor for an image I did not upload Then the metadata fields should be disabled # Evidence: kahuna/public/js/upload/jobs/required-metadata-editor.html lines 14, 44, 71, 101, 124, 152, 173 From fb208b24234c0bbe74dbd871d07649106ff5fafd Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:13:41 +0000 Subject: [PATCH 11/18] Colocate variable definitions with usage, and use lower camel case --- e2e-tests/steps/upload/upload.steps.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/e2e-tests/steps/upload/upload.steps.ts b/e2e-tests/steps/upload/upload.steps.ts index 2a87ca289e..00bc19a7a7 100644 --- a/e2e-tests/steps/upload/upload.steps.ts +++ b/e2e-tests/steps/upload/upload.steps.ts @@ -2,13 +2,7 @@ import type { DataTable } from 'playwright-bdd'; import { Given, KAHUNA_APP_URL, Then, When, expect } from '../fixtures.ts'; import { testImages, uploadPage } from './setup.ts'; -/** Example label offered by the prompt, from kahuna/public/js/strings.json. */ -const EXAMPLE_LABEL = 'Observer'; - -const FILES_TO_UPLOAD = [testImages.smaller, testImages.larger]; - -/** Long enough for a scenario's assertions, short enough not to drag out teardown. */ -const UPLOAD_HOLD_MS = 5_000; +const filesToUpload = [testImages.smaller, testImages.larger]; // --------------------------------------------------------------------------- // Upload page shell @@ -74,11 +68,13 @@ Then('my previous search should be intact', async ({ page, testContext }) => { Given('I have an upload in progress', async ({ page }) => { // Hold the transfer to the ingest bucket open, otherwise the job reaches a terminal // state within a second or so and is no longer "in progress" by the time we assert. + // This is long enough for a scenario's assertions, short enough not to drag out teardown. + const uploadHoldMs = 5_000; await page.route( (url) => url.hostname.startsWith('localstack.'), async (route) => { if (route.request().method() !== 'PUT') return route.fallback(); - await new Promise((resolve) => setTimeout(resolve, UPLOAD_HOLD_MS)); + await new Promise((resolve) => setTimeout(resolve, uploadHoldMs)); await route.abort(); }, ); @@ -135,7 +131,8 @@ Given('I have not applied any preset labels', async ({ page }) => { }); Then('I should see a suggested example label to apply to all uploads', async ({ page }) => { - await expect(uploadPage(page).prompt).toContainText(`label e.g. ${EXAMPLE_LABEL}`); + // The example label offered by the prompt comes from kahuna/public/js/strings.json. + await expect(uploadPage(page).prompt).toContainText(`label e.g. Observer`); }); // --------------------------------------------------------------------------- @@ -154,11 +151,11 @@ Then('the system file picker should open', async ({ testContext }) => { }); When('I select one or more image files to upload', async ({ page }) => { - await uploadPage(page).fileInput.setInputFiles(FILES_TO_UPLOAD.map(({ path }) => path)); + await uploadPage(page).fileInput.setInputFiles(filesToUpload.map(({ path }) => path)); }); Then('those files should be queued for upload', async ({ page }) => { - for (const { fileName } of FILES_TO_UPLOAD) { + for (const { fileName } of filesToUpload) { await expect(uploadPage(page).job(fileName)).toBeVisible(); } }); @@ -177,7 +174,7 @@ Given('an upload size limit is configured', async ({ page }) => { }); When('I select a file that is larger than the size limit', async ({ page }) => { - await uploadPage(page).fileInput.setInputFiles(FILES_TO_UPLOAD.map(({ path }) => path)); + await uploadPage(page).fileInput.setInputFiles(filesToUpload.map(({ path }) => path)); }); Then('I should be warned that the oversized file will be skipped', async ({ testContext }) => { From 7aa175cf2074b775c0e4856f23b57e4a7effe5ca Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:14:41 +0000 Subject: [PATCH 12/18] Shorter comments --- e2e-tests/steps/upload/upload.steps.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e-tests/steps/upload/upload.steps.ts b/e2e-tests/steps/upload/upload.steps.ts index 00bc19a7a7..9760880e37 100644 --- a/e2e-tests/steps/upload/upload.steps.ts +++ b/e2e-tests/steps/upload/upload.steps.ts @@ -4,9 +4,9 @@ import { testImages, uploadPage } from './setup.ts'; const filesToUpload = [testImages.smaller, testImages.larger]; -// --------------------------------------------------------------------------- -// Upload page shell -// --------------------------------------------------------------------------- +/** + * Upload page shell + */ Given('I am permitted to upload images', async ({ page }) => { // Kahuna decides whether to show the upload tools by looking for a `loader` link on the @@ -112,9 +112,9 @@ Then('I should be warned that uploads are in progress and asked to confirm', asy .toContain('You have uploads in progress. Are you sure you want to leave this page?'); }); -// --------------------------------------------------------------------------- -// File upload prompt -// --------------------------------------------------------------------------- +/** + * File upload prompt + */ Then('I should see a message telling me to drag and drop or click to upload to the system', async ({ page }) => { const systemName = await page.evaluate(() => window._clientConfig.systemName); @@ -135,9 +135,9 @@ Then('I should see a suggested example label to apply to all uploads', async ({ await expect(uploadPage(page).prompt).toContainText(`label e.g. Observer`); }); -// --------------------------------------------------------------------------- -// Select-files uploader -// --------------------------------------------------------------------------- +/** + * Select-files uploader + */ When('I click the {string} button', async ({ page, testContext }, label: string) => { testContext.fileChooser = page.waitForEvent('filechooser'); From 67f7d42ed550a49a99c825456e7d9ecf23926044 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:17:52 +0000 Subject: [PATCH 13/18] Remove the prompt test and changes, which can be another PR --- e2e-tests/features/upload.feature | 1 + e2e-tests/steps/upload/upload.steps.ts | 10 ---------- kahuna/public/js/upload/prompt/prompt.html | 2 +- kahuna/public/js/upload/prompt/prompt.js | 9 ++++----- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index 299c08a941..28c102b344 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -77,6 +77,7 @@ Feature: Uploading images to the Grid # Evidence: kahuna/public/js/upload/prompt/prompt.html lines 1-3 # Evidence: kahuna/public/js/upload/prompt/prompt.js lines 20 + @todo Scenario: The prompt suggests an example label when no labels are applied Given I have not applied any preset labels When the upload page loads diff --git a/e2e-tests/steps/upload/upload.steps.ts b/e2e-tests/steps/upload/upload.steps.ts index 9760880e37..8b7ae917ac 100644 --- a/e2e-tests/steps/upload/upload.steps.ts +++ b/e2e-tests/steps/upload/upload.steps.ts @@ -125,16 +125,6 @@ Then('I should see a message telling me to drag and drop or click to upload to t await expect(uploadPage(page).prompt).toContainText(`to get your images on ${systemName}.`); }); -Given('I have not applied any preset labels', async ({ page }) => { - await page.evaluate(() => window.localStorage.removeItem('preset-labels')); - await page.reload(); -}); - -Then('I should see a suggested example label to apply to all uploads', async ({ page }) => { - // The example label offered by the prompt comes from kahuna/public/js/strings.json. - await expect(uploadPage(page).prompt).toContainText(`label e.g. Observer`); -}); - /** * Select-files uploader */ diff --git a/kahuna/public/js/upload/prompt/prompt.html b/kahuna/public/js/upload/prompt/prompt.html index 98475b5933..4ce1492b97 100644 --- a/kahuna/public/js/upload/prompt/prompt.html +++ b/kahuna/public/js/upload/prompt/prompt.html @@ -4,7 +4,7 @@

Apply - label e.g. {{exampleLabel}} + label e.g. {{exampleLabel}}

to all uploads

diff --git a/kahuna/public/js/upload/prompt/prompt.js b/kahuna/public/js/upload/prompt/prompt.js index 4022704a3f..06f8fd7324 100644 --- a/kahuna/public/js/upload/prompt/prompt.js +++ b/kahuna/public/js/upload/prompt/prompt.js @@ -1,16 +1,16 @@ import angular from 'angular'; import './prompt.css'; -import '../../components/gr-preset-labels/gr-preset-labels'; import template from './prompt.html'; +import '../../components/gr-preset-labels/gr-preset-labels'; + import strings from '../../strings.json'; export let prompt = angular.module('kahuna.upload.prompt', [ - 'gr.presetLabels', - 'kahuna.services.presetLabel' + 'gr.presetLabels' ]); -prompt.directive('filePrompt', ['presetLabelService', function (presetLabelService) { +prompt.directive('filePrompt', [function () { return { restrict: 'E', transclude: 'replace', @@ -19,7 +19,6 @@ prompt.directive('filePrompt', ['presetLabelService', function (presetLabelServi link: function($scope) { $scope.systemName = window._clientConfig.systemName; $scope.exampleLabel = strings.exampleLabel; - $scope.hasPresetLabels = () => presetLabelService.getLabels().length > 0; } }; }]); From 606337c12c811f89c471afdf9d12433b81601d06 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:30:58 +0000 Subject: [PATCH 14/18] Use list reporter to view timings in CI --- e2e-tests/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 4887d15bb6..9e48019693 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -6,8 +6,8 @@ "type": "module", "scripts": { "dev:e2e": "node --experimental-strip-types --disable-warning=ExperimentalWarning setup/dev.ts", - "test": "bddgen && playwright test", - "test:headed": "bddgen && playwright test --headed", + "test": "bddgen && playwright test --reporter list", + "test:headed": "bddgen && playwright test --headed --reporter list", "test:report": "playwright show-report", "test:ui": "bddgen && playwright test --ui-port 6080", "test:log": "bddgen && DEBUG=testcontainers* playwright test --ui-port 6080" From 26c803b3a20371392f5d60afb493c5300b700f80 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 13:44:55 +0100 Subject: [PATCH 15/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- e2e-tests/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 9e48019693..439d0420e3 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -6,8 +6,8 @@ "type": "module", "scripts": { "dev:e2e": "node --experimental-strip-types --disable-warning=ExperimentalWarning setup/dev.ts", - "test": "bddgen && playwright test --reporter list", - "test:headed": "bddgen && playwright test --headed --reporter list", + "test": "bddgen && playwright test --reporter=list,html", + "test:headed": "bddgen && playwright test --headed --reporter=list,html", "test:report": "playwright show-report", "test:ui": "bddgen && playwright test --ui-port 6080", "test:log": "bddgen && DEBUG=testcontainers* playwright test --ui-port 6080" From 7617a0638f62470ebf1c509b0019c39b6ec326dc Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:51:18 +0000 Subject: [PATCH 16/18] Add assertion that the current uploads UI is not visible if no uploads are active to the 'An authorised user sees the upload tools' scenario --- e2e-tests/features/upload.feature | 3 ++- e2e-tests/steps/upload/upload.steps.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e-tests/features/upload.feature b/e2e-tests/features/upload.feature index 28c102b344..c9be5cf33a 100644 --- a/e2e-tests/features/upload.feature +++ b/e2e-tests/features/upload.feature @@ -19,6 +19,7 @@ Feature: Uploading images to the Grid Then I should see the file upload prompt And I should see my past 50 uploads And the drag-and-drop uploader should be active + And I should not see the current uploads section # Evidence: kahuna/public/js/upload/view.html lines 11-12, 19-27, 31 # Evidence: kahuna/public/js/upload/controller.js lines 40-45 @@ -41,7 +42,7 @@ Feature: Uploading images to the Grid And my previous search should be intact # Evidence: kahuna/public/js/upload/view.html lines 3-6 - Scenario: The current uploads section only appears while an upload is running + Scenario: The current uploads section appears while an upload is running Given I have an upload in progress When the upload page loads Then I should see my current uploads section diff --git a/e2e-tests/steps/upload/upload.steps.ts b/e2e-tests/steps/upload/upload.steps.ts index 8b7ae917ac..edf68cbcd9 100644 --- a/e2e-tests/steps/upload/upload.steps.ts +++ b/e2e-tests/steps/upload/upload.steps.ts @@ -37,6 +37,10 @@ Then('the drag-and-drop uploader should be active', async ({ page }) => { await expect(uploadPage(page).dragAndDropUploader).toBeAttached(); }); +Then('I should not see the current uploads section', async ({ page }) => { + await expect(uploadPage(page).currentUploads).not.toBeVisible(); +}); + Given( 'I had searched for {string} before opening the upload page', async ({ page, testContext }, query: string) => { From 4c3c1cc685e2d19b20bad5df833a2be8fafdf1ca Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 12:52:29 +0000 Subject: [PATCH 17/18] Test for specific user in query params --- e2e-tests/steps/upload/upload.steps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/steps/upload/upload.steps.ts b/e2e-tests/steps/upload/upload.steps.ts index edf68cbcd9..bd9665f13c 100644 --- a/e2e-tests/steps/upload/upload.steps.ts +++ b/e2e-tests/steps/upload/upload.steps.ts @@ -96,7 +96,7 @@ When('I choose {string}', async ({ page }, label: string) => { }); Then('I should be taken to a search filtered to images I uploaded', async ({ page }) => { - await expect(page).toHaveURL((url) => Boolean(url.searchParams.get('uploadedBy'))); + await expect(page).toHaveURL((url) => url.searchParams.get('uploadedBy') === 'johndoe@example.com'); }); When( From acaaad67243e97a84c459ef70c7f557ae4cd875d Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Tue, 15 Sep 2026 15:23:25 +0000 Subject: [PATCH 18/18] Always start caddy, which is necessary for Playwright to run tests locally --- e2e-tests/setup/stack.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e-tests/setup/stack.ts b/e2e-tests/setup/stack.ts index c7f3512d90..f56e532893 100644 --- a/e2e-tests/setup/stack.ts +++ b/e2e-tests/setup/stack.ts @@ -508,7 +508,6 @@ export async function startStack(options: StartStackOptions = {}): Promise !proxy && 'using dev-nginx', task: async (ctx) => { const caddy = await proxyContainer(ctx.network!, buildCaddyfile(ctx.coreStackProps!)).start(); ctx.containers.push(caddy);
You are not authorised to upload images. Send an email to support if you think this is incorrect.