Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions css/gfz-cd.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@
border-color: #00589C !important;
}

/* Bootstrap outline-primary (#0d6efd) is 4.24:1 on the gray card header.
GFZ blue matches .btn-primary and clears the 4.5:1 text contrast minimum. */
#button-ggms-gfc-upload {
--bs-btn-color: #00589C;
--bs-btn-border-color: #00589C;
--bs-btn-hover-color: #ffffff;
--bs-btn-hover-bg: #00589C;
--bs-btn-hover-border-color: #00589C;
--bs-btn-active-color: #ffffff;
--bs-btn-active-bg: #004a82;
--bs-btn-active-border-color: #004a82;
--bs-btn-disabled-color: #00589C;
--bs-btn-disabled-border-color: #00589C;
color: #00589C;
border-color: #00589C;
}

/* Farbanpassung der aktiven Dropdown Items */
.dropdown-item.active,
.dropdown-item:active {
Expand Down
17 changes: 13 additions & 4 deletions playwright.gem.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { defineConfig, devices } from '@playwright/test';
* TEST SCOPE:
* features, flows (excl. minimal-data-submission, contact-person-roundtrip,
* save-optional-formgroups), shared formgroups (excl. spatial-temporal-coverages,
* resource-type-ernie), formgroups/elmogem-specific, flows/elmogem-specific.
* Individual tests that require ERNIE or generic-only flows are excluded via grepInvert.
* resource-type-ernie, descriptions), formgroups/elmogem-specific, flows/elmogem-specific.
* Individual tests that require ERNIE, a larger generic catalog, or a map
* container are excluded via grepInvert. GEM descriptions are covered by
* formgroups/elmogem-specific/elmogem-descriptions.spec.ts.
*/

const BASE_URL = process.env.BASE_URL ?? 'http://localhost:8080/';
Expand Down Expand Up @@ -57,9 +59,16 @@ export default defineConfig({
'**/contact-person-roundtrip.spec.ts',
'**/resource-type-ernie.spec.ts',
'**/save-optional-formgroups.spec.ts',
// GEM descriptions are static on the page. These tests wait for ERNIE
// accordion items that carry data-description-slug.
'**/formgroups/descriptions.spec.ts',
],
// Skip individual tests that are generic-only or require ERNIE without GEM context
grepInvert: /Test Navbar Dropdown Functionality|validation-failed modal does NOT appear when all|renders static Abstract and dynamic description types|description types API returns valid data|License dropdown filters for software/,
// Skip individual tests that are generic-only or do not match the GEM stage catalog.
// - Navbar / validation / software license: generic-only flows
// - License count and resource-type list: GEM exposes 4 licenses and Dataset only
// - Map console checks: spatial coverage is off, so map.js logs
// "Map initialization failed" while the form itself still loads
grepInvert: /Test Navbar Dropdown Functionality|validation-failed modal does NOT appear when all|License dropdown filters for software|License dropdown should not contain duplicate entries|Test dropdown fields functionality|no JavaScript errors on initial page load|uploads DataCite 4\.7 XML and verifies all major fields are populated|can save again after loading a previously saved XML file/,
},
],
});
6 changes: 3 additions & 3 deletions tests/playwright/features/elmo-performance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import type { Page } from '@playwright/test';
import { promises as fs } from 'fs';
import path from 'path';
import { waitForHomepageReady } from '../utils';
import { REPO_ROOT, waitForHomepageReady } from '../utils';

const KEY_SECTIONS = [
'header[role="banner"]',
Expand Down Expand Up @@ -76,14 +76,14 @@ const collectNavigationTiming = async (page: Page): Promise<NavigationTiming> =>
test.describe('Homepage performance', () => {
test('keeps optimized header assets within the initial-load budget', async () => {
const assetStats = await Promise.all(
HEADER_LOGO_ASSETS.map(asset => fs.stat(path.join(process.cwd(), asset))),
HEADER_LOGO_ASSETS.map(asset => fs.stat(path.join(REPO_ROOT, asset))),
);
const totalBytes = assetStats.reduce((sum, stats) => sum + stats.size, 0);

expect(totalBytes).toBeLessThanOrEqual(HEADER_LOGO_BUDGET_BYTES);

for (const legacyAsset of LEGACY_HEADER_LOGO_ASSETS) {
const legacyAssetExists = await fs.access(path.join(process.cwd(), legacyAsset))
const legacyAssetExists = await fs.access(path.join(REPO_ROOT, legacyAsset))
.then(() => true)
.catch(() => false);
expect(legacyAssetExists, `${legacyAsset} should be removed`).toBe(false);
Expand Down
4 changes: 2 additions & 2 deletions tests/playwright/features/header-responsive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test.describe('Header Responsive Design', () => {
for (const viewport of viewports) {
test(`should display all header elements on ${viewport.name} (${viewport.width}x${viewport.height})`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/');
await page.goto('');
await page.waitForSelector('header.navbar', { timeout: 5000 });

const gfzLogo = page.locator('header a[href="https://www.gfz.de/"] img[alt="GFZ Logo"]');
Expand Down Expand Up @@ -41,7 +41,7 @@ test.describe('Header Responsive Design', () => {

test('should center GFZ Data Services logo on small screens (< 768px)', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
await page.goto('');
await page.waitForSelector('header.navbar', { timeout: 5000 });

const logo = page.locator('header .logo-center-wrapper');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function expectedDropdownNames(primary: string[], shared: string[], includeConta
}

async function fetchRoleNames(page: Page, type: 'person' | 'institution' | 'both'): Promise<string[]> {
const response = await page.request.get(`/api/v2/vocabs/roles?type=${type}`);
const response = await page.request.get(`api/v2/vocabs/roles?type=${type}`);
expect(response.ok()).toBeTruthy();
return roleNames(await response.json());
}
Expand All @@ -49,7 +49,7 @@ test('contributor role dropdowns match the roles API exactly', async ({ page })
const institutionApiResponse = await fetchRoleNames(page, 'institution');
const sharedRoles = await fetchRoleNames(page, 'both');

await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
const includeInstitutionContact = await page.evaluate(
() => (window as any).ELMO_FEATURES?.showContactInstitution === true
Expand Down
20 changes: 10 additions & 10 deletions tests/playwright/formgroups/contributors-redesign.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';

test('Contributors starts empty and shares contact status with Authors', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack && (window as any).authorStack));
test.skip((await page.locator('[data-contributor-add-type]').count()) < 2, 'Both Contributor types are required for this flow.');
const cards = page.locator('[data-contributor-card]');
Expand Down Expand Up @@ -38,7 +38,7 @@ test('Contributors starts empty and shares contact status with Authors', async (
});

test('disabled institution contact role does not become active on restore', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
test.skip(await page.evaluate(() => (window as any).ELMO_FEATURES?.showContactInstitution === true),
'This case applies only when institution contacts are disabled.');
Expand All @@ -54,7 +54,7 @@ test('disabled institution contact role does not become active on restore', asyn
});

test('contributor summaries show person and building icons instead of type badges', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
await page.evaluate(() => (window as any).contributorStack.setContributors([
{ type: 'person', familyname: 'Doe', roles: [] },
Expand All @@ -73,7 +73,7 @@ test('contributor summaries show person and building icons instead of type badge

test('help icons follow the first visible contributor field of each kind', async ({ page }) => {
await page.addInitScript(() => localStorage.setItem('helpStatus', 'help-on'));
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
await page.evaluate(() => (window as any).contributorStack.setContributors([
{ type: 'person', familyname: 'First', roles: [] },
Expand Down Expand Up @@ -116,7 +116,7 @@ test('help icons follow the first visible contributor field of each kind', async

test('contact email and website use the Authors help icons', async ({ page }) => {
await page.addInitScript(() => localStorage.setItem('helpStatus', 'help-on'));
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
await page.evaluate(() => (window as any).contributorStack.setContributors([
{ type: 'person', familyname: 'First', roles: ['Contact Person'] },
Expand All @@ -139,7 +139,7 @@ test('contact email and website use the Authors help icons', async ({ page }) =>
});

test('enabled institution contact role updates both headers', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
test.skip(await page.evaluate(() => (window as any).ELMO_FEATURES?.showContactInstitution !== true),
'This case applies only when institution contacts are enabled.');
Expand All @@ -158,7 +158,7 @@ test('enabled institution contact role updates both headers', async ({ page }) =
});

test('dragging the handle changes contributor order and saved payload', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
const cards = page.locator('[data-contributor-card]');
await page.evaluate(() => (window as any).contributorStack.setContributors([
Expand All @@ -180,7 +180,7 @@ test('person role picker excludes institution-only roles', async ({ page }) => {
const names = type === 'person' ? ['Researcher'] : type === 'institution' ? ['Distributor'] : ['Data Collector'];
return route.fulfill({ json: names.map(name => ({ name })) });
});
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
await page.locator('[data-contributor-add-type="institution"]').click();
await page.locator('[data-contributor-add-type="person"]').click();
Expand All @@ -192,7 +192,7 @@ test('person role picker excludes institution-only roles', async ({ page }) => {
});

test('contact fields and editable affiliations follow the requested rows', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
await page.locator('[data-contributor-add-type="person"]').click();
await page.locator('[data-contributor-add-type="institution"]').click();
Expand Down Expand Up @@ -220,7 +220,7 @@ test('contact fields and editable affiliations follow the requested rows', async
});

test('collapsed contributor keeps only a compact summary and actions', async ({ page }) => {
await page.goto('/');
await page.goto('');
await page.waitForFunction(() => Boolean((window as any).contributorStack));
await page.evaluate(() => (window as any).contributorStack.setContributors([
{ type: 'person', familyname: 'Doe', roles: [] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const TEST_XML = `<?xml version="1.0" encoding="UTF-8"?>
</subjects>
</resource>`;

const TEST_ROUTE_PATH = '/thesauri-roundtrip-test';
const TEST_ROUTE_PATH = 'thesauri-roundtrip-test';

const TEST_PAGE_HTML = `<!DOCTYPE html>
<html lang="en">
Expand Down Expand Up @@ -114,7 +114,7 @@ async function triggerTranslationsAndWaitForThesauri(page: import('@playwright/t

test.describe('Thesaurus Keywords Roundtrip (Issue #1043)', () => {
test.beforeEach(async ({ page }) => {
await page.route(`**${TEST_ROUTE_PATH}`, async route => {
await page.route(`**/${TEST_ROUTE_PATH}`, async route => {
await route.fulfill({ status: 200, contentType: 'text/html', body: TEST_PAGE_HTML });
});
await page.route('**/api/v2/vocabs/thesauri/availability', async route => {
Expand Down Expand Up @@ -165,7 +165,7 @@ test.describe('Thesaurus Keywords Roundtrip (Issue #1043)', () => {
}
});

await page.addScriptTag({ url: '/js/thesauri.js', type: 'module' });
await page.addScriptTag({ url: 'js/thesauri.js', type: 'module' });

await triggerTranslationsAndWaitForThesauri(page);
});
Expand Down
6 changes: 3 additions & 3 deletions tests/playwright/formgroups/thesauri-keywords.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const MOCK_INSTRUMENTS = {
},
],
};
const TEST_ROUTE_PATH = '/thesauri-keywords-test';
const TEST_ROUTE_PATH = 'thesauri-keywords-test';
const TEST_PAGE_HTML = `<!DOCTYPE html>
<html lang="en">
<head>
Expand Down Expand Up @@ -148,7 +148,7 @@ async function waitForThesauriInit(page: import('@playwright/test').Page) {

test.describe('Thesauri Keywords Form Group', () => {
test.beforeEach(async ({ page }) => {
await page.route(`**${TEST_ROUTE_PATH}`, async route => {
await page.route(`**/${TEST_ROUTE_PATH}`, async route => {
await route.fulfill({
status: 200,
contentType: 'text/html',
Expand Down Expand Up @@ -217,7 +217,7 @@ test.describe('Thesauri Keywords Form Group', () => {
};
});

await page.addScriptTag({ url: '/js/thesauri.js', type: 'module' });
await page.addScriptTag({ url: 'js/thesauri.js', type: 'module' });

// Set up language handlers and fire translationsLoaded to trigger dynamic init
await page.evaluate(() => {
Expand Down
8 changes: 4 additions & 4 deletions tests/playwright/formgroups/title-type-ernie.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ test.describe('Title Type Dropdown - ERNIE Integration', () => {

test('Title Type API endpoint returns valid data', async ({ page }) => {
// Directly test the API endpoint
const response = await page.request.get('/api/v2/vocabs/titletypes');
const response = await page.request.get('api/v2/vocabs/titletypes');

expect(response.ok()).toBe(true);
expect(response.status()).toBe(200);
Expand All @@ -87,7 +87,7 @@ test.describe('Title Type Dropdown - ERNIE Integration', () => {
});

test('Title Type API response does not expose ernie_id', async ({ page }) => {
const response = await page.request.get('/api/v2/vocabs/titletypes');
const response = await page.request.get('api/v2/vocabs/titletypes');

expect(response.ok()).toBe(true);
const data = await response.json();
Expand All @@ -102,7 +102,7 @@ test.describe('Title Type Dropdown - ERNIE Integration', () => {
});

test('Title Type API response contains Main Title', async ({ page }) => {
const response = await page.request.get('/api/v2/vocabs/titletypes');
const response = await page.request.get('api/v2/vocabs/titletypes');

expect(response.ok()).toBe(true);
const data = await response.json();
Expand All @@ -114,7 +114,7 @@ test.describe('Title Type Dropdown - ERNIE Integration', () => {

test.describe('Title Type ERNIE Cache Admin Endpoints', () => {
test('Cache status endpoint returns configuration info', async ({ page }) => {
const response = await page.request.get('/api/v2/admin/cache/titletypes/status');
const response = await page.request.get('api/v2/admin/cache/titletypes/status');

// This endpoint may require auth in production
if (response.ok()) {
Expand Down
7 changes: 4 additions & 3 deletions tests/playwright/setup/variant-settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync, writeFileSync } from 'fs';
import path from 'path';
import { REPO_ROOT } from '../utils/constants';

export type Variant = 'generic' | 'gem' | 'msl' | 'igsn';

Expand Down Expand Up @@ -59,10 +60,10 @@ const VARIANT_SETTINGS: Record<Variant, VariantConfig> = {
},
};

/** Path to settings.php, resolved from the project root via process.cwd(). */
const SETTINGS_PHP = path.join(process.cwd(), 'settings.php');
/** settings.php in the ELMO project root, beside this test tree. */
const SETTINGS_PHP = path.join(REPO_ROOT, 'settings.php');
/** Lock file applied after env overrides in settings.php / settings.elmo.php. */
const VARIANT_LOCK_PHP = path.join(process.cwd(), 'playwright-variant-lock.php');
const VARIANT_LOCK_PHP = path.join(REPO_ROOT, 'playwright-variant-lock.php');

const MSL_DERIVED_FLAGS = [
'showMslLabs',
Expand Down
Loading