Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ac5e036
Remove redundant spec
jonathonherbert Sep 11, 2026
f6b1db7
Tag unimplemented upload scenarios @todo and skip them
jonathonherbert Sep 10, 2026
6520661
Implement the runnable upload e2e scenarios
jonathonherbert Sep 10, 2026
7b371f0
Restore the uploads-in-progress leave warning scenario
jonathonherbert Sep 10, 2026
8b5af0b
Show the upload prompt's example label when no labels are applied
jonathonherbert Sep 10, 2026
1eda379
Minor scenario changes to accommodate testing requirements
jonathonherbert Sep 14, 2026
f361d4f
Adapt tests to feature changes
jonathonherbert Sep 14, 2026
e1dd82d
Move comments out of todo directives
jonathonherbert Sep 15, 2026
e94a877
Move uploads steps to own folder
jonathonherbert Sep 15, 2026
208587b
Add detail to metadata w/o edito permission scenario
jonathonherbert Sep 15, 2026
fb208b2
Colocate variable definitions with usage, and use lower camel case
jonathonherbert Sep 15, 2026
7aa175c
Shorter comments
jonathonherbert Sep 15, 2026
67f7d42
Remove the prompt test and changes, which can be another PR
jonathonherbert Sep 15, 2026
606337c
Use list reporter to view timings in CI
jonathonherbert Sep 15, 2026
26c803b
Potential fix for pull request finding
jonathonherbert Sep 15, 2026
7617a06
Add assertion that the current uploads UI is not visible if no upload…
jonathonherbert Sep 15, 2026
4c3c1cc
Test for specific user in query params
jonathonherbert Sep 15, 2026
acaaad6
Always start caddy, which is necessary for Playwright to run tests lo…
jonathonherbert Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -35,12 +36,13 @@ 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
# 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
Expand All @@ -52,6 +54,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:
Expand All @@ -71,13 +78,15 @@ 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
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 via the 'apply label to all uploads' button
Then that label should be applied to all my uploads
Expand Down Expand Up @@ -149,7 +158,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
Expand All @@ -158,14 +168,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
Expand Down Expand Up @@ -357,7 +369,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
Expand Down
Binary file added e2e-tests/fixtures/images/test-card-f.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e-tests/fixtures/images/test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,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"
Expand Down
3 changes: 3 additions & 0 deletions e2e-tests/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});

/**
Expand Down
1 change: 0 additions & 1 deletion e2e-tests/setup/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,6 @@ export async function startStack(options: StartStackOptions = {}): Promise<GridE
},
{
title: 'Start reverse proxy',
skip: () => !proxy && 'using dev-nginx',
task: async (ctx) => {
const caddy = await proxyContainer(ctx.network!, buildCaddyfile(ctx.coreStackProps!)).start();
ctx.containers.push(caddy);
Expand Down
32 changes: 32 additions & 0 deletions e2e-tests/steps/common.steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Given, KAHUNA_APP_URL, expect } from './fixtures.ts';

Given('the application stack is running', async ({ request }) => {
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: '/',
},
]);

// 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');
});
9 changes: 7 additions & 2 deletions e2e-tests/steps/fixtures.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -12,6 +12,11 @@ export const KAHUNA_APP_URL = `https://media.${DOMAIN}`;
interface TestContext {
response?: APIResponse;
mediaApiResponse?: Promise<Response>;
/** Messages from alerts and confirms, in the order the page raised them. */
dialogs: string[];
fileChooser?: Promise<FileChooser>;
/** The query run on the search page before navigating to the upload page. */
previousSearchQuery?: string;
}

/**
Expand All @@ -21,7 +26,7 @@ interface TestContext {
*/
export const test = base.extend<{ testContext: TestContext }>({
testContext: async ({}, use) => {
await use({});
await use({ dialogs: [] });
},
});

Expand Down
15 changes: 0 additions & 15 deletions e2e-tests/steps/global-setup.ts

This file was deleted.

52 changes: 52 additions & 0 deletions e2e-tests/steps/upload/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { statSync } from 'node:fs';
import * as path from 'node:path';
import type { Page } from '@playwright/test';

declare global {
interface Window {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this could come from the application definition 🤔

_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 }),
/** 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` }),
};
};
Loading
Loading