Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.
Merged
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
1 change: 0 additions & 1 deletion openspec/specs/org-archimate-export/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,6 @@ The GET endpoint MUST accept boolean query parameters that control which data is
- AND the API MUST treat `1`, `yes`, `true`, `TRUE` as truthy values

### Requirement: Frontend MUST provide organization export with data layer toggles
@e2e exclude SPA does not mount: webpack runtime chunk not loaded in templates (GH issue #322)
The ArchiMate settings section MUST include checkboxes for selecting which data layers to include, and trigger the export via the GET endpoint.

#### Scenario: User triggers organization export with toggles
Expand Down
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@nextcloud/stylelint-config": "^2.4.0",
"@nextcloud/webpack-vue-config": "^6.0.1",
"@pinia/testing": "^0.1.3",
"@playwright/test": "^1.60.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.12",
"@typescript-eslint/parser": "^7.18.0",
Expand Down
11 changes: 10 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@
// SPDX-FileCopyrightText: 2026 Conduction B.V.

import { defineConfig, devices } from '@playwright/test'
import * as path from 'path'

/**
* Playwright configuration for softwarecatalog e2e tests.
* Base URL: http://localhost:8080 (Nextcloud dev container)
*
* globalSetup logs in once and persists session state to
* tests/e2e/.auth/admin.json so all specs start pre-authenticated.
*/
export default defineConfig({
testDir: './tests/e2e',
globalSetup: path.resolve(__dirname, 'tests/e2e/global-setup.ts'),
timeout: 60_000,
expect: { timeout: 15_000 },
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: 1,
reporter: 'list',
outputDir: 'test-results',
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:8080',
baseURL: process.env.BASE_URL ?? process.env.NEXTCLOUD_URL ?? 'http://localhost:8080',
storageState: path.resolve(__dirname, 'tests/e2e/.auth/admin.json'),
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
Expand Down
7 changes: 7 additions & 0 deletions templates/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
use OCP\Util;

$appId = OCA\SoftwareCatalog\AppInfo\Application::APP_ID;
// The webpack build emits a separate runtime chunk (runtimeChunk: { name: 'runtime' })
// plus shared vendor/nc-vue chunks. All must be loaded in dependency order BEFORE
// the entry chunk, otherwise __webpack_require__ is never bootstrapped and Vue never
// mounts. See GH issue #322 for the full diagnosis.
Util::addScript($appId, $appId . '-runtime');
Util::addScript($appId, $appId . '-shared-vendor');
Util::addScript($appId, $appId . '-shared-nc-vue');
Util::addScript($appId, $appId . '-main');
Util::addStyle($appId, 'main');
?>
Expand Down
9 changes: 8 additions & 1 deletion templates/settings/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
use OCP\Util;

$appId = OCA\SoftwareCatalog\AppInfo\Application::APP_ID;
// The webpack build emits a separate runtime chunk (runtimeChunk: { name: 'runtime' })
// plus shared vendor/nc-vue chunks. All must be loaded in dependency order BEFORE
// the entry chunk, otherwise __webpack_require__ is never bootstrapped and Vue never
// mounts. See GH issue #322 for the full diagnosis.
Util::addScript($appId, $appId . '-runtime');
Util::addScript($appId, $appId . '-shared-vendor');
Util::addScript($appId, $appId . '-shared-nc-vue');
Util::addScript($appId, $appId . '-settings');
Util::addStyle($appId, 'main');

?>

<div id="settings"></div>
<div id="settings"></div>
65 changes: 65 additions & 0 deletions tests/e2e/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2026 Conduction B.V.
/**
* Playwright globalSetup — logs into Nextcloud once and persists the resulting
* cookie jar to `tests/e2e/.auth/admin.json` so every spec reuses the session.
*
* Nextcloud 34 renders the login form via JavaScript so the inputs don't appear
* in the static HTML. We must wait for the input fields to hydrate before filling.
*/

import { chromium, type FullConfig } from '@playwright/test'
import * as path from 'path'
import * as fs from 'fs'

const AUTH_DIR = path.resolve(__dirname, '.auth')
const STORAGE_STATE = path.join(AUTH_DIR, 'admin.json')

export default async function globalSetup(config: FullConfig): Promise<void> {
const baseURL = (config.projects[0]?.use?.baseURL as string | undefined)
?? process.env.NEXTCLOUD_URL
?? 'http://localhost:8080'
const username = process.env.NC_ADMIN_USER ?? 'admin'
const password = process.env.NC_ADMIN_PASS ?? 'admin'

fs.mkdirSync(AUTH_DIR, { recursive: true })

const browser = await chromium.launch()
const context = await browser.newContext({ baseURL })
const page = await context.newPage()

// Nextcloud 34 renders the login form entirely via JavaScript.
// Load the page and wait until the input fields hydrate before filling.
await page.goto('/index.php/login', { timeout: 60_000 })

// Try multiple selector strategies covering different NC versions:
// - NC 34+ JS-rendered login: inputs get autocomplete attributes
// - NC 28-30 server-rendered: inputs have name="user" / name="password"
const userSelector = 'input[autocomplete="username"], input[name="user"], input[id="user"]'
const passSelector = 'input[autocomplete="current-password"], input[type="password"], input[name="password"]'

await page.locator(userSelector).waitFor({ state: 'visible', timeout: 45_000 })
await page.locator(userSelector).fill(username)
await page.locator(passSelector).fill(password)

// Click the submit button (may be "Log in" or just type="submit")
const submitSelector = 'button[type="submit"], button:has-text("Log in"), input[type="submit"]'
await page.locator(submitSelector).first().click()

// Wait for redirect away from /login — the #header only renders when authenticated
await page.waitForFunction(
() => !window.location.pathname.includes('/login'),
{ timeout: 30_000 },
)

if (page.url().includes('/login')) {
throw new Error(
`Nextcloud login failed — still on ${page.url()}. `
+ 'Check NC_ADMIN_USER / NC_ADMIN_PASS env vars (default: admin/admin).',
)
}

// Persist cookies + localStorage for test reuse
await context.storageState({ path: STORAGE_STATE })
await browser.close()
}
Loading