Quellcode durchsuchen

Clean up console plugin template

Penta vor 2 Wochen
Ursprung
Commit
7dc90155c1

+ 0 - 4
.ci-operator.yaml

@@ -1,4 +0,0 @@
-build_root_image:
-  name: console-plugin-test-cypress
-  namespace: ci
-  tag: node22

+ 0 - 20
.devcontainer/Dockerfile.console

@@ -1,20 +0,0 @@
-FROM quay.io/openshift/origin-console:latest
-COPY --from=openshift/origin-cli:latest /usr/bin/oc /usr/local/bin/oc
-
-ENV OC_URL=$OC_URL
-ENV OC_PASS=$OC_PASS
-ENV OC_USER=$OC_USER
-ENV OC_PLUGIN_NAME=$OC_PLUGIN_NAME
-
-USER root
-CMD eval "oc login $OC_URL -u $OC_USER -p $OC_PASS --insecure-skip-tls-verify" && \
-    /opt/bridge/bin/bridge -public-dir=/opt/bridge/static \
-    -i18n-namespaces plugin__$OC_PLUGIN_NAME \
-    -plugins $OC_PLUGIN_NAME=http://localhost:9001 \
-    -k8s-mode-off-cluster-thanos=$(oc -n openshift-config-managed get configmap monitoring-shared-config -o jsonpath='{.data.thanosPublicURL}') \
-    -k8s-mode-off-cluster-endpoint=$(oc whoami --show-server) \
-    -k8s-mode-off-cluster-skip-verify-tls=true \
-    -k8s-auth-bearer-token=$(oc whoami --show-token) \
-    -k8s-auth="bearer-token" \
-    -user-auth="disabled" \
-    -k8s-mode="off-cluster"

+ 0 - 2
.devcontainer/Dockerfile.plugin

@@ -1,2 +0,0 @@
-FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:16 as build
-COPY --from=openshift/origin-cli:latest /usr/bin/oc /usr/local/bin/oc

+ 0 - 31
.devcontainer/devcontainer.json

@@ -1,31 +0,0 @@
-{
-  "name": "Console + Plugin",
-  "dockerComposeFile": "docker-compose.yml",
-  "service": "plugin",
-  "workspaceFolder": "/workspace",
-
-  "initializeCommand": ".devcontainer/init.sh",
-  "postCreateCommand": "yarn && eval 'oc login $OC_URL -u $OC_USER -p $OC_PASS --insecure-skip-tls-verify'",
-  "forwardPorts": [9000, 9001],
-  "portsAttributes": {
-    "9000": {
-      "label": "Console"
-    },
-    "9001": {
-      "label": "Plugin static files",
-      "onAutoForward": "silent"
-    }
-  },
-  "features": {},
-  "customizations": {
-    "vscode": {
-      "settings": {},
-      "extensions": [
-        "ms-azuretools.vscode-docker",
-        "ms-vscode.vscode-typescript-next",
-        "dbaeumer.vscode-eslint",
-        "esbenp.prettier-vscode"
-      ]
-    }
-  }
-}

+ 0 - 34
.devcontainer/docker-compose.yml

@@ -1,34 +0,0 @@
-version: '3.8'
-# create dev.env with the following values:
-# OC_URL
-# OC_USER
-# OC_PASS
-# OC_PLUGIN_NAME
-services:
-  console:
-    build:
-      context: ..
-      dockerfile: .devcontainer/Dockerfile.console
-    env_file: dev.env
-    restart: unless-stopped
-    healthcheck:
-      test: oc whoami
-      interval: 1m30s
-      timeout: 10s
-      retries: 5
-
-  plugin:
-    build:
-      context: ..
-      dockerfile: .devcontainer/Dockerfile.plugin
-    env_file: dev.env
-    depends_on:
-      - console
-    network_mode: service:console
-    # Overrides default command so things don't shut down after the process ends.
-    command: sleep infinity
-    # Cache local workspace and copy shell history.
-    volumes:
-      - ..:/workspace:cached
-      - ~/.bash_history:/root/.bash_history
-      - ~/.zsh_history:/root/.zsh_history

+ 0 - 35
.devcontainer/init.sh

@@ -1,35 +0,0 @@
-#!/bin/bash
-
-if [[ ! -f .devcontainer/dev.env ]]
-then
-    cat << EOF
-    env file 'dev.env' does not exist in .devcontainer, please create it and add the the correct values for your cluster.
-    OC_PLUGIN_NAME=my-plugin
-    OC_URL=https://api.example.com:6443
-    OC_USER=kubeadmin
-    OC_PASS=<password>
-EOF
-    exit 2
-else
-    echo 'found 'dev.env' in .devcontainer'
-fi
-
-# if one of the variables are missing, abort the build.
-success=1
-! grep -q OC_PLUGIN_NAME= ".devcontainer/dev.env" && success=0
-! grep -q OC_URL= ".devcontainer/dev.env" && success=0
-! grep -q OC_USER= ".devcontainer/dev.env" && success=0
-! grep -q OC_PASS= ".devcontainer/dev.env" && success=0
-
-if ((success)); then
-    echo 'dev.env is formatted correctly, proceeding.'
-else
-    cat << EOF
-    dev.env is not formatted correctly, please add the the correct values for your cluster.
-    OC_PLUGIN_NAME=my-plugin
-    OC_URL=https://api.example.com:6443
-    OC_USER=kubeadmin
-    OC_PASS=<password>
-EOF
-exit 2
-fi

+ 0 - 224
AGENTS.md

@@ -1,224 +0,0 @@
-# AI Agent Instructions for OpenShift Console Plugin Template
-
-This document provides context and guidelines for AI coding assistants working on this codebase.
-
-## Project Overview
-
-This is a **template repository** for creating OpenShift Console dynamic plugins. It's meant to be used via GitHub's "Use this template" feature, NOT forked. The template provides a minimal starting point for extending the OpenShift Console UI with custom pages and functionality.
-
-> **⚠️ WARNING:**
-> This repository is used by multiple large-scale enterprise web applications. Please proceed with caution when making any changes to this codebase. Changes here can affect downstream projects that depend on this template.
->
-> **Only make changes that should be standard practice for ALL plugins created from this template.** If a change is specific to one plugin use case, it belongs in the instantiated plugin repository, not in this template.
-
-**Key Technologies:**
-- TypeScript + React 18
-- PatternFly 6 (UI component library)
-- Webpack 5 with Module Federation
-- react-i18next for internationalization
-- Playwright for e2e testing
-- Helm for deployment
-
-**Compatibility:** Requires OpenShift 4.12+ (uses ConsolePlugin CRD v1 API)
-
-## Architecture & Patterns
-
-### Dynamic Plugin System
-
-This plugin uses webpack module federation to load at runtime into the OpenShift Console. Key files:
-
-- `console-extensions.json`: Declares what the plugin adds to console (routes, nav items, etc.)
-- `package.json` `consolePlugin` section: Plugin metadata and exposed modules mapping
-- `webpack.config.ts`: Configures module federation and build
-
-**Critical:** Any component referenced in `console-extensions.json` must have a corresponding entry in `package.json` under `consolePlugin.exposedModules`.
-
-### Component Structure
-
-- Use functional components with hooks (NO class components)
-- All components should be TypeScript (`.tsx`)
-- Follow PatternFly component patterns
-- Use PatternFly CSS variables instead of hex colors (dark mode compatibility)
-
-### Styling Constraints
-
-**IMPORTANT:** The `.stylelintrc.yaml` enforces strict rules to prevent breaking console:
-
-- **NO hex colors** - use PatternFly CSS variables (e.g., `var(--pf-v6-global-palette--blue-500)`)
-- **NO naked element selectors** (like `table`, `div`) - prevents overwriting console styles
-- **NO `.pf-` or `.co-` prefixed classes** - these are reserved for PatternFly and console
-- **Prefix all custom classes** with plugin name (e.g., `console-plugin-template__nice`)
-
-Don't disable these rules without understanding they protect against layout breakage!
-
-## Internationalization (i18n)
-
-**Namespace Convention:** `plugin__<plugin-name>` (e.g., `plugin__console-plugin-template`)
-
-### In React Components:
-```tsx
-const { t } = useTranslation('plugin__console-plugin-template');
-return <h1>{t('Hello, World!')}</h1>;
-```
-
-### In console-extensions.json:
-```json
-"name": "%plugin__console-plugin-template~My Label%"
-```
-
-**After adding/changing messages:** Run `yarn i18n` to update locale files in `/locales`
-
-## File Organization
-
-```
-src/
-  components/          # React components
-    ExamplePage.tsx   # Example page component
-    *.css            # Component styles (scoped with plugin prefix)
-console-extensions.json # Plugin extension declarations
-package.json           # Plugin metadata in consolePlugin section
-tsconfig.json          # TypeScript config (strict: false currently)
-webpack.config.ts      # Module federation + build config
-locales/               # i18n translation files
-charts/                # Helm chart for deployment
-integration-tests/     # Playwright e2e tests
-```
-
-## Development Workflow
-
-### Local Development
-1. `yarn install` - install dependencies
-2. `yarn start` - starts webpack dev server on port 9001 with CORS
-3. `yarn start-console` - runs OpenShift console in container (requires cluster login)
-4. Navigate to http://localhost:9000/example
-
-### Code Quality
-- `yarn lint` - runs eslint, prettier, and stylelint (with --fix)
-- Linting is mandatory before commits
-- Follow existing code patterns in the repo
-
-### Testing
-- `yarn test` - runs Jest unit tests
-- `yarn test-e2e` - opens Playwright in headed mode
-- `yarn test-e2e-headless` - runs Playwright in headless mode
-- Add e2e tests for new pages/features
-
-## TypeScript Configuration
-
-Current config has `strict: true` and enforces:
-- `noUnusedLocals: true`
-- All files should use `.tsx` extension
-
-## Common Development Tasks
-
-### Adding a New Page
-1. Create component in `src/components/MyPage.tsx`
-2. Add to `package.json` `exposedModules`: `"MyPage": "./components/MyPage"`
-3. Add route in `console-extensions.json`:
-   ```json
-   {
-     "type": "console.page/route",
-     "properties": {
-       "path": "/my-page",
-       "component": { "$codeRef": "MyPage" }
-     }
-   }
-   ```
-4. Optional: Add nav item in `console-extensions.json`
-5. Run `yarn i18n` if you added translatable strings
-
-### Adding a Navigation Item
-```json
-{
-  "type": "console.navigation/href",
-  "properties": {
-    "id": "my-nav-item",
-    "name": "%plugin__console-plugin-template~My Page%",
-    "href": "/my-page",
-    "perspective": "admin",
-    "section": "home"
-  }
-}
-```
-
-### Updating Plugin Name
-When instantiating from template, update:
-1. `package.json` - `name` and `consolePlugin.name`
-2. `package.json` - `consolePlugin.displayName` and `description`
-3. All i18n namespace references (`plugin__<name>`)
-4. CSS class prefixes
-5. Helm chart values
-
-## Build & Deployment
-
-### Building Image
-```bash
-docker build -t quay.io/my-repository/my-plugin:latest .
-# For Apple Silicon: add --platform=linux/amd64
-```
-
-### Deploying via Helm
-```bash
-helm upgrade -i my-plugin charts/openshift-console-plugin \
-  -n my-namespace \
-  --create-namespace \
-  --set plugin.image=my-plugin-image-location
-```
-
-**Note:** OpenShift 4.10 requires `--set plugin.securityContext.enabled=false`
-
-## Important Constraints & Gotchas
-
-1. **Template, not fork:** Users should use "Use this template", not fork
-2. **i18n namespace must match ConsolePlugin resource name** with `plugin__` prefix
-3. **CSS class prefixes prevent style conflicts** - always prefix with plugin name
-4. **Module federation requires exact module mapping** - `exposedModules` must match `$codeRef` values
-5. **PatternFly CSS variables only** - hex colors break dark mode
-6. **No webpack HMR for extensions** - changes to `console-extensions.json` require restart
-7. **React 18** - matches console's React version
-
-## Extension Points
-
-See [Console Plugin SDK README](https://github.com/openshift/console/tree/master/frontend/packages/console-dynamic-plugin-sdk) for available extension types:
-
-- `console.page/route` - add new pages
-- `console.navigation/href` - add nav items
-- `console.navigation/section` - add nav sections
-- `console.tab` - add tabs to resource pages
-- `console.action/provider` - add actions to resources
-- `console.flag` - feature flags
-- Many more...
-
-## Code Style Preferences
-
-- Functional components with hooks (NO classes)
-- TypeScript for all new files
-- Use PatternFly components whenever possible
-- Keep components focused and composable
-- Prefer named exports for components
-- Use `React.FC` or explicit return types
-- CSS-in-files (not CSS-in-JS)
-
-## Testing Strategy
-
-- **E2E tests (Playwright):** For user flows and page rendering
-- **Unit tests (Jest):** For component logic and plugin metadata
-- **Test data attributes:** Use `data-test` attributes for selectors (`testIdAttribute` is configured in `playwright.config.ts`)
-- Run tests locally before opening PRs
-
-## References
-
-- [Console Plugin SDK](https://github.com/openshift/console/tree/master/frontend/packages/console-dynamic-plugin-sdk)
-- [PatternFly React](https://www.patternfly.org/get-started/develop)
-- [Dynamic Plugin Enhancement Proposal](https://github.com/openshift/enhancements/blob/master/enhancements/console/dynamic-plugins.md)
-
-## Quick Decision Guide
-
-**When should I...**
-
-- **Use this template?** When creating a NEW OpenShift Console plugin from scratch
-- **Add a page?** Update console-extensions.json + exposedModules + create component
-- **Style something?** Use PatternFly components and CSS variables, prefix custom classes
-- **Add translations?** Use `t()` function, run `yarn i18n` after
-- **Test changes?** Run locally with `yarn start` + `yarn start-console`, add Playwright tests
-- **Deploy?** Build image, push to registry, install via Helm chart

+ 0 - 1
CLAUDE.md

@@ -1 +0,0 @@
-AGENTS.md

+ 0 - 10
OWNERS

@@ -1,10 +0,0 @@
-approvers:
-  - christianvogt
-  - florkbr
-  - jhadvig
-  - logonoff
-  - rhamilto
-  - spadgett
-  - TheRealJon
-  - vojtechszocs
-component: Management Console

+ 1 - 1
i18next-parser.config.js

@@ -9,7 +9,7 @@ module.exports = {
   locales: ['en'],
   namespaceSeparator: '~',
   reactNamespace: false,
-  defaultNamespace: 'plugin__console-plugin-template',
+  defaultNamespace: 'plugin__homelab-dashboard',
   useKeysAsDefaultValue: true,
 
   // see below for more details

+ 0 - 62
integration-tests/pages/login.ts

@@ -1,62 +0,0 @@
-import type { Locator, Page } from '@playwright/test';
-
-declare global {
-  interface Window {
-    SERVER_FLAGS?: {
-      authDisabled?: boolean;
-    };
-  }
-}
-
-export const KUBEADMIN_USERNAME = 'kubeadmin';
-
-export class LoginPage {
-  constructor(private readonly page: Page) {}
-
-  private async isAuthDisabled() {
-    return this.page.evaluate(() => window.SERVER_FLAGS?.authDisabled);
-  }
-
-  // Fill a field via CDP to avoid exposing the value in Playwright traces
-  // https://github.com/microsoft/playwright/issues/19992#issuecomment-4078945450
-  private async fillSensitive(locator: Locator, text: string) {
-    await locator.focus();
-    const cdpSession = await this.page.context().newCDPSession(this.page);
-    await cdpSession.send('Input.dispatchKeyEvent', {
-      type: 'keyDown',
-      key: 'a',
-      commands: ['selectAll'],
-    });
-    await cdpSession.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'a' });
-    await cdpSession.send('Input.insertText', { text });
-    await cdpSession.detach();
-  }
-
-  async login(
-    username: string = KUBEADMIN_USERNAME,
-    password: string = process.env.BRIDGE_KUBEADMIN_PASSWORD ?? '',
-  ) {
-    await this.page.context().clearCookies();
-    await this.page.goto('/');
-
-    if (await this.isAuthDisabled()) {
-      return;
-    }
-
-    await this.page.locator('[data-test-id="login"]').waitFor({ state: 'visible' });
-    await this.page.locator('#inputUsername').fill(username);
-    await this.fillSensitive(this.page.locator('#inputPassword'), password);
-    await this.page.locator('button[type=submit]').click();
-    await this.page.getByTestId('username').waitFor({ state: 'attached' });
-  }
-
-  async logout() {
-    if (await this.isAuthDisabled()) {
-      return;
-    }
-    await this.page.getByTestId('username').click();
-    await this.page.getByTestId('log-out').waitFor({ state: 'visible' });
-    // eslint-disable-next-line playwright/no-force-option -- dropdown may be covered by overlay
-    await this.page.getByTestId('log-out').click({ force: true });
-  }
-}

+ 0 - 9
integration-tests/support/index.ts

@@ -1,9 +0,0 @@
-import type { Page } from '@playwright/test';
-import { expect } from '@playwright/test';
-
-export async function checkErrors(page: Page) {
-  const windowError = await page.evaluate(
-    () => (window as Window & { windowError?: string }).windowError,
-  );
-  expect(windowError, 'Console JS error detected').toBeUndefined();
-}

+ 0 - 12
integration-tests/tests/auth.setup.ts

@@ -1,12 +0,0 @@
-import { test as setup } from '@playwright/test';
-import { LoginPage } from '../pages/login';
-
-// eslint-disable-next-line playwright/expect-expect -- setup test saves storageState, no assertions needed
-setup('authenticate', async ({ page }) => {
-  const loginPage = new LoginPage(page);
-  await loginPage.login();
-
-  await page.getByTestId('tour-step-footer-secondary').filter({ hasText: 'Skip tour' }).click();
-
-  await page.context().storageState({ path: 'integration-tests/.auth/user.json' });
-});

+ 0 - 73
integration-tests/tests/example-page.spec.ts

@@ -1,73 +0,0 @@
-import { execSync } from 'child_process';
-import { test, expect } from '@playwright/test';
-import { checkErrors } from '../support';
-
-const PLUGIN_TEMPLATE_NAME = 'console-plugin-template';
-// Defined in openshift/release ci-operator config as CYPRESS_PLUGIN_TEMPLATE_PULL_SPEC
-const PLUGIN_TEMPLATE_PULL_SPEC =
-  process.env.PLUGIN_TEMPLATE_PULL_SPEC ?? process.env.CYPRESS_PLUGIN_TEMPLATE_PULL_SPEC;
-
-const isLocalDevEnvironment = (process.env.BRIDGE_BASE_ADDRESS ?? 'http://localhost:9000').includes(
-  'localhost',
-);
-
-function exec(command: string, timeoutMs = 360000) {
-  try {
-    return execSync(command, { timeout: timeoutMs, encoding: 'utf-8' });
-  } catch (e) {
-    console.error('Command failed:', command, e);
-    return '';
-  }
-}
-
-function installHelmChart(helmPath: string) {
-  const result = exec(
-    `${helmPath} upgrade -i ${PLUGIN_TEMPLATE_NAME} charts/openshift-console-plugin -n ${PLUGIN_TEMPLATE_NAME} --create-namespace --set plugin.image=${PLUGIN_TEMPLATE_PULL_SPEC}`,
-  );
-  console.log('Helm install:', result);
-
-  exec(
-    `oc rollout status -n ${PLUGIN_TEMPLATE_NAME} deploy/${PLUGIN_TEMPLATE_NAME} -w --timeout=300s`,
-  );
-  exec('oc rollout status -w deploy/console -n openshift-console --timeout=300s');
-}
-
-function deleteHelmChart(helmPath: string) {
-  const result = exec(
-    `${helmPath} uninstall ${PLUGIN_TEMPLATE_NAME} -n ${PLUGIN_TEMPLATE_NAME} && oc delete namespaces ${PLUGIN_TEMPLATE_NAME}`,
-  );
-  console.log('Helm uninstall:', result);
-}
-
-test.describe('Console plugin template test', () => {
-  test.beforeAll(() => {
-    if (!isLocalDevEnvironment) {
-      console.log('this is not a local env, installing helm');
-      exec('./install_helm.sh');
-      installHelmChart('/tmp/helm');
-    } else {
-      console.log('this is a local env, not installing helm');
-      installHelmChart('helm');
-    }
-  });
-
-  test.afterEach(async ({ page }) => {
-    await checkErrors(page);
-  });
-
-  test.afterAll(() => {
-    if (!isLocalDevEnvironment) {
-      deleteHelmChart('/tmp/helm');
-    } else {
-      deleteHelmChart('helm');
-    }
-  });
-
-  test('Verify the example page title', async ({ page }) => {
-    await page.goto('/');
-    await page.locator('[data-quickstart-id="qs-nav-home"]').click();
-    await page.getByTestId('nav').getByText('Plugin example').click();
-    await expect(page).toHaveURL(/\/example/);
-    await expect(page).toHaveTitle(/Hello, plugin!/);
-  });
-});

+ 0 - 10
integration-tests/tsconfig.json

@@ -1,10 +0,0 @@
-{
-    "extends": "../tsconfig.json",
-    "compilerOptions": {
-        "noEmit": true,
-        "types": ["node"],
-        "isolatedModules": false
-    },
-    "include": ["./**/*.ts"],
-    "exclude": ["screenshots", ".auth"]
-}

+ 0 - 0
locales/en/plugin__console-plugin-template.json → locales/en/plugin__homelab-dashboard.json


+ 4 - 4
package.json

@@ -1,12 +1,12 @@
 {
-  "name": "console-plugin-template",
-  "version": "0.0.1",
-  "description": "Template project for OpenShift Console plugins",
+  "name": "homelab-dashboard",
+  "version": "0.0.2",
+  "description": "Custom administration dashboard for the homelab OpenShift cluster",
   "private": true,
   "license": "Apache-2.0",
   "repository": {
     "type": "git",
-    "url": "https://github.com/openshift/console-plugin-template.git"
+    "url": "https://git.pentou.eu/penta/homelab-dashboard.git"
   },
   "scripts": {
     "clean": "rm -rf dist",

+ 0 - 32
playwright.config.ts

@@ -1,32 +0,0 @@
-import { defineConfig } from '@playwright/test';
-
-export default defineConfig({
-  testDir: './integration-tests/tests',
-  timeout: 30000,
-  retries: 1,
-  use: {
-    baseURL: process.env.BRIDGE_BASE_ADDRESS ?? 'http://localhost:9000',
-    viewport: { width: 1920, height: 1080 },
-    screenshot: 'on',
-    ignoreHTTPSErrors: true,
-    video: 'retain-on-failure',
-    trace: 'on',
-    testIdAttribute: 'data-test',
-  },
-  projects: [
-    { name: 'setup', testMatch: /auth\.setup\.ts/ },
-    {
-      name: 'chromium',
-      use: {
-        browserName: 'chromium',
-        storageState: 'integration-tests/.auth/user.json',
-      },
-      dependencies: ['setup'],
-    },
-  ],
-  reporter: [
-    ['list'],
-    ['html', { outputFolder: 'integration-tests/results/html', open: 'never' }],
-    ['junit', { outputFile: 'integration-tests/results/junit-results.xml' }],
-  ],
-});

+ 0 - 37
test-prow-e2e.sh

@@ -1,37 +0,0 @@
-#!/usr/bin/env bash
-
-set -exuo pipefail
-
-ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts}
-TEST_RESULTS_DIR=integration-tests/results
-INSTALLER_DIR=${INSTALLER_DIR:=${ARTIFACT_DIR}/installer}
-
-function copyArtifacts {
-  if [ -d "$ARTIFACT_DIR" ] && [ -d "$TEST_RESULTS_DIR" ]; then
-    if [[ -z "$(ls -A -- "$TEST_RESULTS_DIR")" ]]; then
-      echo "No artifacts were copied."
-    else
-      echo "Copying artifacts from $(pwd)..."
-      cp -r "$TEST_RESULTS_DIR" "${ARTIFACT_DIR}"
-    fi
-  fi
-}
-
-trap copyArtifacts EXIT
-
-
-# don't log kubeadmin-password
-set +x
-BRIDGE_KUBEADMIN_PASSWORD="$(cat "${KUBEADMIN_PASSWORD_FILE:-${INSTALLER_DIR}/auth/kubeadmin-password}")"
-export BRIDGE_KUBEADMIN_PASSWORD
-set -x
-BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')"
-export BRIDGE_BASE_ADDRESS
-
-echo "Install dependencies"
-if [ ! -d node_modules ]; then
-  yarn install --immutable
-fi
-
-echo "Runs Playwright tests in headless mode"
-yarn test-e2e-headless