Skip to content
Draft
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
48 changes: 48 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,54 @@ npm run cypress:open

For detailed testing instructions, see `web/cypress/CYPRESS_TESTING_GUIDE.md`

### Cypress Component Testing

#### Overview

Cypress component tests mount individual React components in isolation, without requiring a running OpenShift cluster. They are useful for testing component rendering, user interactions, and visual behavior with fast feedback.

- **Test location**: `web/cypress/component/`
- **Support file**: `web/cypress/support/component.ts`
- **Config**: `component` section in `web/cypress.config.ts`

#### When to Create Component Tests

- Testing a component's rendering logic (conditional display, empty states)
- Verifying props are handled correctly
- Validating user interactions within a single component
- When E2E tests would be overkill for the behavior under test

#### Quick Test Commands

```bash
cd web

# Interactive mode
npm run cypress:open:component

# Headless mode - all component tests
npm run cypress:run:component

# Run a single component test file
npx cypress run --component --spec cypress/component/labels.cy.tsx
```

#### Writing a Component Test

Component test files use the `.cy.tsx` extension and go in `web/cypress/component/`:

```typescript
import React from 'react';
import { MyComponent } from '../../src/components/MyComponent';

describe('MyComponent', () => {
it('renders correctly', () => {
cy.mount(<MyComponent prop="value" />);
cy.contains('expected text').should('be.visible');
});
});
```

### Release Pipeline:

- **Konflux**: Handles CI/CD and release automation
Expand Down
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,23 @@ cd web/cypress
npm run cypress:run --spec "cypress/e2e/**/regression/**"
```

### Component Tests (Cypress)

For testing individual React components in isolation (no cluster required):

- Test files: `web/cypress/component/` (`.cy.tsx` extension)
- Support file: `web/cypress/support/component.ts`

```bash
cd web

# Interactive mode
npm run cypress:open:component

# Headless mode
npm run cypress:run:component
```

---

## Internationalization (i18n)
Expand Down
13 changes: 13 additions & 0 deletions config/perses-dashboards.patch.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,18 @@
"component": { "$codeRef": "DashboardPage" }
}
}
},
{
"op": "add",
"path": "/extensions/1",
"value": {
"type": "ols.tool-ui",
"properties": {
"id": "mcp-obs/show-timeseries",
"component": {
"$codeRef": "ols-tool-ui.ShowTimeseries"
}
}
}
}
]
58 changes: 58 additions & 0 deletions web/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as fs from 'fs-extra';
import * as console from 'console';
import * as path from 'path';
import registerCypressGrep from '@cypress/grep/src/plugin';
import { DefinePlugin, NormalModuleReplacementPlugin } from 'webpack';

export default defineConfig({
screenshotsFolder: './cypress/screenshots',
Expand Down Expand Up @@ -159,4 +160,61 @@ export default defineConfig({
experimentalMemoryManagement: true,
experimentalStudio: true,
},
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
webpackConfig: {
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@perses-dev/plugin-system': path.resolve(__dirname, 'cypress/component/mocks/perses-plugin-system.tsx'),
'@perses-dev/dashboards': path.resolve(__dirname, 'cypress/component/mocks/perses-dashboards.tsx'),
'@perses-dev/prometheus-plugin': path.resolve(__dirname, 'cypress/component/mocks/perses-prometheus-plugin.ts'),
},
},
module: {
rules: [
{
test: /\.(jsx?|tsx?)$/,
exclude: /node_modules/,
use: { loader: 'swc-loader' },
},
{
test: /\.scss$/,
exclude: /node_modules\/(?!(@patternfly|@openshift-console\/plugin-shared)\/).*/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff2?|ttf|eot|otf)(\?.*$|$)/,
type: 'asset/resource',
},
{
test: /\.m?js/,
resolve: { fullySpecified: false },
},
],
},
plugins: [
new DefinePlugin({
'process.env.I18N_NAMESPACE': JSON.stringify('plugin__monitoring-plugin'),
}),
new NormalModuleReplacementPlugin(
/helpers\/OlsToolUIPersesWrapper/,
path.resolve(__dirname, 'cypress/component/mocks/OlsToolUIPersesWrapper.tsx'),
),
new NormalModuleReplacementPlugin(
/helpers\/AddToDashboardButton/,
path.resolve(__dirname, 'cypress/component/mocks/AddToDashboardButton.tsx'),
),
],
},
},
specPattern: './cypress/component/**/*.cy.{js,jsx,ts,tsx}',
supportFile: './cypress/support/component.ts',
},
});
72 changes: 67 additions & 5 deletions web/cypress/CYPRESS_TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ The Monitoring Plugin uses a 3-layer architecture for test organization:

```
cypress/
├── component/ # Component tests (isolated, no cluster needed)
├── e2e/
│ ├── monitoring/ # Core monitoring tests (Administrator)
│ │ ├── 00.bvt_admin.cy.ts
Expand All @@ -81,7 +82,9 @@ cypress/
│ │ ├── 02.reg_metrics.cy.ts
│ │ └── 03.reg_legacy_dashboards.cy.ts
│ ├── perses/ # COO/Perses scenarios
│ └── commands/ # Custom Cypress commands
│ ├── commands/ # Custom Cypress commands
│ ├── component.ts # Component test support (mount command)
│ └── component-index.html # HTML template for component mounting
└── views/ # Page object models (reusable actions)
```

Expand All @@ -92,7 +95,65 @@ cypress/

---

## Creating Tests
## Component Testing

Component tests mount individual React components in isolation using Cypress, without requiring a running OpenShift cluster. They provide fast feedback for rendering logic, props handling, and interactions.

### When to Use Component Tests vs E2E Tests

| Use Component Tests When | Use E2E Tests When |
|---|---|
| Testing rendering and visual output | Testing full user workflows |
| Verifying props and conditional display | Testing navigation between pages |
| Validating empty/error states | Testing API integration |
| Fast feedback during development | Testing cross-component interactions |

### Writing Component Tests

Component test files use the `.cy.tsx` extension and live in `cypress/component/`:

```typescript
import React from 'react';
import { Labels } from '../../src/components/labels';

describe('Labels', () => {
it('renders "No labels" when labels is empty', () => {
cy.mount(<Labels labels={{}} />);
cy.contains('No labels').should('be.visible');
});

it('renders a single label', () => {
cy.mount(<Labels labels={{ app: 'monitoring' }} />);
cy.contains('app').should('be.visible');
cy.contains('monitoring').should('be.visible');
});
});
```

### Running Component Tests

```bash
cd web

# Interactive mode (GUI) - best for development
npm run cypress:open:component

# Headless mode - best for CI
npm run cypress:run:component

# Run a single component test file
npx cypress run --component --spec cypress/component/labels.cy.tsx
```

### Key Differences from E2E Tests

- **No cluster required**: Components are mounted directly in the browser
- **Custom mount command**: Use `cy.mount(<Component />)` instead of `cy.visit()`
- **Support file**: Uses `cypress/support/component.ts` (not `cypress/support/index.ts`)

---

## Creating E2E Tests

### Workflow

Expand Down Expand Up @@ -133,11 +194,12 @@ export const runAlertTests = (perspective: string) => {

| Scenario | Action |
|----------|--------|
| New UI feature | Create new test scenario in support/ |
| New UI feature | Create new E2E test scenario in support/ |
| Bug fix | Add test case to existing support file |
| Component update | Update existing test scenarios |
| New Perses feature | Create new test scenario in support/ |
| ACM integration | Add test in e2e/coo/ |
| New Perses feature | Create new E2E test scenario in support/ |
| ACM integration | Add E2E test in e2e/coo/ |
| Isolated component logic | Add component test in component/ |

### Best Practices

Expand Down
18 changes: 16 additions & 2 deletions web/cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,20 +407,34 @@ export CYPRESS_SESSION=true

---

## Component Testing

Cypress component tests mount individual React components in isolation, without a running cluster. They provide fast feedback for testing rendering logic, props handling, and user interactions. See **[CYPRESS_TESTING_GUIDE.md](CYPRESS_TESTING_GUIDE.md)** for more guidance on how
to write and run the tests.

### Configuration

Component testing is configured in the `component` section of `web/cypress.config.ts`. It uses a standalone webpack config with `swc-loader` and a custom `mount` command (compatible with React 17) defined in `cypress/support/component.ts`.

---

## Test Organization

### Directory Structure

```
cypress/
├── e2e/ # Test files by perspective
├── component/ # Component test files (.cy.tsx)
├── e2e/ # E2E test files by perspective
│ ├── monitoring/ # Core monitoring (Administrator)
│ ├── coo/ # COO-specific tests
│ └── virtualization/ # Virtualization integration
├── support/ # Reusable test scenarios
│ ├── monitoring/ # Test scenario modules
│ ├── perses/ # Perses scenarios
│ └── commands/ # Custom Cypress commands
│ ├── commands/ # Custom Cypress commands
│ ├── component.ts # Component test support
│ └── component-index.html # Component test HTML template
├── views/ # Page object models
├── fixtures/ # Test data and mocks
└── E2E_TEST_SCENARIOS.md # Complete test catalog
Expand Down
44 changes: 44 additions & 0 deletions web/cypress/component/labels.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Labels } from '../../src/components/labels';
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is mostly just for reference on writing component tests.


describe('Labels', () => {
it('renders "No labels" when labels is empty', () => {
cy.mount(<Labels labels={{}} />);
cy.contains('No labels').should('be.visible');
});

it('renders "No labels" when labels is undefined', () => {
cy.mount(<Labels labels={undefined} />);
cy.contains('No labels').should('be.visible');
});

it('renders a single label', () => {
cy.mount(<Labels labels={{ app: 'monitoring' }} />);
cy.contains('app').should('be.visible');
cy.contains('monitoring').should('be.visible');
});

it('renders multiple labels', () => {
const labels = {
app: 'monitoring',
env: 'production',
team: 'platform',
};
cy.mount(<Labels labels={labels} />);

cy.contains('app').should('be.visible');
cy.contains('monitoring').should('be.visible');
cy.contains('env').should('be.visible');
cy.contains('production').should('be.visible');
cy.contains('team').should('be.visible');
cy.contains('platform').should('be.visible');
});

it('renders label with key=value format', () => {
cy.mount(<Labels labels={{ severity: 'critical' }} />);
cy.get('.pf-v6-c-label').within(() => {
cy.contains('severity');
cy.contains('=');
cy.contains('critical');
});
});
});
8 changes: 8 additions & 0 deletions web/cypress/component/mocks/AddToDashboardButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const AddToDashboardButton = ({ query, name, description }) => (
<button
data-testid="add-to-dashboard"
data-name={name}
data-query={query}
data-description={description}
/>
);
7 changes: 7 additions & 0 deletions web/cypress/component/mocks/OlsToolUIPersesWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

export const OlsToolUIPersesWrapper = ({ children, initialTimeRange }) => (
<div data-testid="perses-wrapper" data-time-range={JSON.stringify(initialTimeRange)}>
{children}
</div>
);
10 changes: 10 additions & 0 deletions web/cypress/component/mocks/perses-dashboards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export const Panel = ({ definition, panelOptions }) => (
<div data-testid="mock-panel">
<div data-testid="panel-definition" data-definition={JSON.stringify(definition)} />
{panelOptions?.extra && <div data-testid="panel-extra">{panelOptions.extra()}</div>}
</div>
);

export const VariableProvider = ({ children }) => <>{children}</>;
2 changes: 2 additions & 0 deletions web/cypress/component/mocks/perses-plugin-system.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DataQueriesProvider = ({ children }) => children;
export const TimeRangeProviderBasic = ({ children }) => children;
2 changes: 2 additions & 0 deletions web/cypress/component/mocks/perses-prometheus-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// DurationString is used as a type-only import, so no runtime export needed
export type DurationString = string;
Loading