Skip to content

Commit cde252a

Browse files
authored
Merge pull request #2435 from redpanda-data/chore/e2e-test-variant-kafka
e2e tests variant for Kafka
2 parents 5d849e0 + 606c8a7 commit cde252a

9 files changed

Lines changed: 214 additions & 1 deletion

File tree

frontend/tests/shared/global-setup.mjs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { GenericContainer, Network, Wait } from 'testcontainers';
22

3-
import { KAFKA_CONNECT_IMAGE, OWL_SHOP_IMAGE, REDPANDA_IMAGE } from './test-images.mjs';
3+
import { KAFKA_CONNECT_IMAGE, KAFKA_IMAGE, OWL_SHOP_IMAGE, REDPANDA_IMAGE } from './test-images.mjs';
44
import { exec } from 'node:child_process';
55
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
66
import { dirname, join, resolve } from 'node:path';
@@ -156,6 +156,39 @@ async function verifyRedpandaServices(state, ports) {
156156
}
157157
}
158158

159+
async function startKafkaContainer(network, state, ports, variantName) {
160+
console.log('Starting Apache Kafka container (KRaft, SASL/PLAIN)...');
161+
const jaasConfPath = resolve(__dirname, '..', `test-variant-${variantName}`, 'config', 'kafka_server_jaas.conf');
162+
const kafka = await new GenericContainer(KAFKA_IMAGE)
163+
.withNetwork(network)
164+
.withNetworkAliases('kafka')
165+
.withExposedPorts({ container: 9092, host: ports.kafkaKafka })
166+
.withBindMounts([{ source: jaasConfPath, target: '/etc/kafka/jaas.conf', mode: 'ro' }])
167+
.withEnvironment({
168+
KAFKA_NODE_ID: '1',
169+
KAFKA_PROCESS_ROLES: 'broker,controller',
170+
KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka:9093',
171+
KAFKA_LISTENERS: 'SASL_PLAINTEXT://:9092,CONTROLLER://:9093',
172+
KAFKA_ADVERTISED_LISTENERS: 'SASL_PLAINTEXT://kafka:9092',
173+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'SASL_PLAINTEXT:SASL_PLAINTEXT,CONTROLLER:PLAINTEXT',
174+
KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER',
175+
KAFKA_INTER_BROKER_LISTENER_NAME: 'SASL_PLAINTEXT',
176+
KAFKA_SASL_ENABLED_MECHANISMS: 'PLAIN',
177+
KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: 'PLAIN',
178+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: '1',
179+
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true',
180+
CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk',
181+
KAFKA_OPTS: '-Djava.security.auth.login.config=/etc/kafka/jaas.conf',
182+
})
183+
.withWaitStrategy(Wait.forLogMessage(/Kafka Server started/i))
184+
.withStartupTimeout(120_000)
185+
.start();
186+
187+
state.kafkaId = kafka.getId();
188+
state.kafkaContainer = kafka;
189+
console.log(`✓ Apache Kafka container started: ${state.kafkaId}`);
190+
}
191+
159192
async function startOwlShop(network, state) {
160193
console.log('Starting OwlShop container...');
161194
const owlshopConfigContent = `
@@ -942,6 +975,12 @@ async function cleanupOnFailure(state) {
942975
console.log(`Failed to stop destination Redpanda container: ${error.message}`);
943976
});
944977
}
978+
if (state.kafkaContainer) {
979+
console.log('Stopping Kafka container using testcontainers API...');
980+
await state.kafkaContainer.stop().catch((error) => {
981+
console.log(`Failed to stop Kafka container: ${error.message}`);
982+
});
983+
}
945984
if (state.redpandaContainer) {
946985
console.log('Stopping Redpanda container using testcontainers API...');
947986
await state.redpandaContainer.stop().catch((error) => {
@@ -1002,6 +1041,24 @@ export default async function globalSetup(config = {}) {
10021041
// Setup Docker infrastructure
10031042
const network = await setupDockerNetwork(state);
10041043

1044+
// --- Kafka variant: simple KRaft broker, no Redpanda/owlshop/connect ---
1045+
if (variantConfig.isKafka) {
1046+
await startKafkaContainer(network, state, ports, variantName);
1047+
const backendConfigPath = resolve(__dirname, '..', `test-variant-${variantName}`, 'config', configFile);
1048+
await startBackendServerWithConfig(
1049+
network,
1050+
isEnterprise,
1051+
imageTag,
1052+
state,
1053+
backendConfigPath,
1054+
ports.backend,
1055+
'console-backend'
1056+
);
1057+
writeFileSync(getStateFile(variantName), JSON.stringify(state, null, 2));
1058+
console.log('\n✅ All services ready! Starting tests...\n');
1059+
return;
1060+
}
1061+
10051062
// --- Group 1: Start clusters in parallel ---
10061063
const clusterPromises = [
10071064
startRedpandaContainer(network, state, ports).then(() => verifyRedpandaServices(state, ports)),

frontend/tests/shared/global-teardown.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ export default async function globalTeardown(config = {}) {
7777
});
7878
}
7979

80+
if (state.kafkaId) {
81+
console.log('Stopping Kafka container...');
82+
await execAsync(`docker stop ${state.kafkaId}`).catch(() => {});
83+
await execAsync(`docker rm ${state.kafkaId}`).catch(() => {});
84+
}
85+
8086
// Stop source cluster (existing/main redpanda)
8187
if (state.redpandaId) {
8288
console.log('Stopping source Redpanda container...');

frontend/tests/shared/test-images.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ export const KAFKA_CONNECT_IMAGE = process.env.TEST_IMAGE_KAFKA_CONNECT || forma
4343
* Override with TEST_IMAGE_OWL_SHOP environment variable.
4444
*/
4545
export const OWL_SHOP_IMAGE = process.env.TEST_IMAGE_OWL_SHOP || formatImage(config.images.owlShop);
46+
47+
/**
48+
* Apache Kafka (bitnami) Docker image.
49+
* Override with TEST_IMAGE_KAFKA environment variable.
50+
*/
51+
export const KAFKA_IMAGE = process.env.TEST_IMAGE_KAFKA || formatImage(config.images.kafka);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
serveFrontend: true
2+
3+
kafka:
4+
brokers: ["kafka:9092"]
5+
sasl:
6+
enabled: true
7+
mechanism: PLAIN
8+
username: e2euser
9+
password: very-secret
10+
11+
schemaRegistry:
12+
enabled: false
13+
14+
redpanda:
15+
adminApi:
16+
enabled: false
17+
18+
server:
19+
listenPort: 3000
20+
allowedOrigins: ["http://localhost:3000", "http://localhost:3001", "http://localhost:3002"]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
KafkaServer {
2+
org.apache.kafka.common.security.plain.PlainLoginModule required
3+
username="e2euser"
4+
password="very-secret"
5+
user_e2euser="very-secret";
6+
};
7+
8+
KafkaClient {
9+
org.apache.kafka.common.security.plain.PlainLoginModule required
10+
username="e2euser"
11+
password="very-secret";
12+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "kafka",
3+
"displayName": "Apache Kafka",
4+
"isEnterprise": false,
5+
"isKafka": true,
6+
"needsShadowlink": false,
7+
"needsAuth": false,
8+
"ports": {
9+
"backend": 3002,
10+
"kafkaKafka": 19094
11+
}
12+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
import dotenv from 'dotenv';
3+
4+
dotenv.config();
5+
6+
const reporters = process.env.CI
7+
? [['github' as const], ['html' as const, { outputFolder: 'playwright-report' }]]
8+
: [['markdown' as const], ['html' as const, { outputFolder: 'playwright-report' }]];
9+
10+
const config = defineConfig({
11+
expect: {
12+
timeout: 60 * 1000,
13+
},
14+
15+
testMatch: '**/*.spec.ts',
16+
17+
fullyParallel: !!process.env.CI,
18+
19+
forbidOnly: !!process.env.CI,
20+
21+
retries: process.env.CI ? 1 : 0,
22+
23+
workers: process.env.CI ? 4 : undefined,
24+
25+
reporter: reporters,
26+
27+
globalSetup: '../shared/global-setup.mjs',
28+
globalTeardown: '../shared/global-teardown.mjs',
29+
30+
metadata: {
31+
variant: 'kafka',
32+
variantName: 'kafka',
33+
configFile: 'console.config.yaml',
34+
isEnterprise: false,
35+
isKafka: true,
36+
needsShadowlink: false,
37+
},
38+
39+
use: {
40+
navigationTimeout: 30 * 1000,
41+
actionTimeout: 30 * 1000,
42+
viewport: { width: 1920, height: 1080 },
43+
headless: !!process.env.CI,
44+
45+
baseURL: process.env.REACT_APP_ORIGIN ?? 'http://localhost:3002',
46+
47+
trace: 'retain-on-failure',
48+
screenshot: 'only-on-failure',
49+
video: 'retain-on-failure',
50+
},
51+
52+
projects: [
53+
{
54+
name: 'chromium',
55+
use: {
56+
...devices['Desktop Chrome'],
57+
permissions: ['clipboard-read', 'clipboard-write'],
58+
},
59+
},
60+
],
61+
});
62+
63+
export default config;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { TopicPage } from '../../test-variant-console/utils/topic-page';
4+
5+
test.describe('Topics - Apache Kafka backend', () => {
6+
test('should load the topics list', async ({ page }) => {
7+
const topicPage = new TopicPage(page);
8+
await topicPage.goToTopicsList();
9+
10+
await expect(page.getByTestId('topics-table')).toBeVisible();
11+
});
12+
13+
test('should create and delete a topic', async ({ page }) => {
14+
const topicPage = new TopicPage(page);
15+
const topicName = `kafka-e2e-${Date.now()}`;
16+
17+
await topicPage.createTopic(topicName);
18+
await topicPage.verifyTopicInList(topicName);
19+
await topicPage.deleteTopic(topicName);
20+
await topicPage.verifyTopicNotInList(topicName);
21+
});
22+
23+
test('should search for a topic by name', async ({ page }) => {
24+
const topicPage = new TopicPage(page);
25+
const topicName = `kafka-search-${Date.now()}`;
26+
27+
await topicPage.createTopic(topicName);
28+
await topicPage.searchTopics(topicName);
29+
await topicPage.verifyTopicInList(topicName);
30+
31+
await topicPage.deleteTopic(topicName);
32+
});
33+
});

test-images.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
"owlShop": {
1616
"repository": "quay.io/cloudhut/owl-shop",
1717
"tag": "master"
18+
},
19+
"kafka": {
20+
"repository": "apache/kafka",
21+
"tag": "3.9.0"
1822
}
1923
}
2024
}

0 commit comments

Comments
 (0)