Skip to content

Commit 1ccd78a

Browse files
authored
test: add comprehensive E2E tests for Secrets resource (#3245)
1 parent c7f05f8 commit 1ccd78a

File tree

4 files changed

+466
-0
lines changed

4 files changed

+466
-0
lines changed

e2e/pom/secrets.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { uiGoto } from '@e2e/utils/ui';
18+
import { expect, type Page } from '@playwright/test';
19+
20+
const locator = {
21+
getSecretNavBtn: (page: Page) =>
22+
page.getByRole('link', { name: 'Secrets' }),
23+
getAddSecretBtn: (page: Page) =>
24+
page.getByRole('button', { name: 'Add Secret' }),
25+
getAddBtn: (page: Page) =>
26+
page.getByRole('button', { name: 'Add', exact: true }),
27+
};
28+
29+
const assert = {
30+
isIndexPage: async (page: Page) => {
31+
await expect(page).toHaveURL((url) => url.pathname.endsWith('/secrets'));
32+
const title = page.getByRole('heading', { name: 'Secrets' });
33+
await expect(title).toBeVisible();
34+
},
35+
isAddPage: async (page: Page) => {
36+
await expect(page).toHaveURL((url) =>
37+
url.pathname.endsWith('/secrets/add')
38+
);
39+
const title = page.getByRole('heading', { name: 'Add Secret' });
40+
await expect(title).toBeVisible();
41+
},
42+
isDetailPage: async (page: Page) => {
43+
await expect(page).toHaveURL((url) =>
44+
url.pathname.includes('/secrets/detail')
45+
);
46+
const title = page.getByRole('heading', { name: 'Secret Detail' });
47+
await expect(title).toBeVisible();
48+
},
49+
};
50+
51+
const goto = {
52+
toIndex: (page: Page) => uiGoto(page, '/secrets'),
53+
toAdd: (page: Page) => uiGoto(page, '/secrets/add'),
54+
};
55+
56+
export const secretsPom = {
57+
...locator,
58+
...assert,
59+
...goto,
60+
};
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { secretsPom } from '@e2e/pom/secrets';
18+
import { e2eReq } from '@e2e/utils/req';
19+
import { test } from '@e2e/utils/test';
20+
import { expect } from '@playwright/test';
21+
22+
import { API_SECRETS } from '@/config/constant';
23+
24+
25+
const createdSecretId = 'test-aws-secret-all-fields';
26+
const manager = 'aws';
27+
28+
test.describe('CRUD secret with all fields (AWS)', () => {
29+
test.describe.configure({ mode: 'serial' });
30+
31+
test.afterAll(async () => {
32+
// cleanup: delete the secret
33+
if (createdSecretId) {
34+
await e2eReq.delete(`${API_SECRETS}/${manager}/${createdSecretId}`).catch((err) => {
35+
// ignore 404 error if secret doesn't exist, rethrow others
36+
if (err.response?.status !== 404 && !err.message.includes('404')) {
37+
throw err;
38+
}
39+
});
40+
}
41+
});
42+
43+
test('should create a secret with all fields', async ({ page }) => {
44+
await test.step('create secret via UI', async () => {
45+
await secretsPom.toIndex(page);
46+
await secretsPom.getAddSecretBtn(page).click();
47+
await secretsPom.isAddPage(page);
48+
49+
await page.getByLabel('ID').fill(createdSecretId);
50+
51+
// Select Manager AWS
52+
const managerSection = page.getByRole('group', { name: 'Secret Manager' });
53+
await managerSection.locator('input.mantine-Select-input').click();
54+
await page.getByRole('option', { name: 'aws' }).click();
55+
56+
await page.getByLabel('Access Key ID').fill('AKIAIOSFODNN7EXAMPLE');
57+
await page.getByLabel('Secret Access Key').fill('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
58+
await page.getByLabel('Session Token').fill('test-session-token-123');
59+
await page.getByLabel('Region').fill('us-west-2');
60+
await page.getByLabel('Endpoint URL').fill('https://secretsmanager.us-west-2.amazonaws.com');
61+
62+
await secretsPom.getAddBtn(page).click();
63+
});
64+
65+
await test.step('verify secret appears in UI', async () => {
66+
await secretsPom.isIndexPage(page);
67+
const row = page.locator('tr').filter({ hasText: createdSecretId });
68+
await expect(row).toBeVisible();
69+
});
70+
});
71+
72+
test('should read/view the secret details', async ({ page }) => {
73+
await test.step('navigate to secret details page and verify UI', async () => {
74+
await secretsPom.toIndex(page);
75+
await secretsPom.isIndexPage(page);
76+
77+
const row = page.locator('tr').filter({ hasText: createdSecretId });
78+
await row.getByRole('button', { name: 'View' }).click();
79+
await secretsPom.isDetailPage(page);
80+
81+
const pageContent = await page.textContent('body');
82+
expect(pageContent).toContain('Secret Manager');
83+
await expect(page.locator('input[name="id"]')).toHaveValue(createdSecretId);
84+
// Verify AWS-specific fields are present (labels)
85+
expect(pageContent).toContain('Access Key ID');
86+
expect(pageContent).toContain('Secret Access Key');
87+
expect(pageContent).toContain('Region');
88+
});
89+
});
90+
91+
test('should update the secret with new values', async ({ page }) => {
92+
const updatedFields = {
93+
'Access Key ID': 'AKIAI44QH8DHBEXAMPLE',
94+
'Secret Access Key': 'je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY',
95+
'Session Token': 'updated-session-token-456',
96+
'Region': 'eu-west-1',
97+
'Endpoint URL': 'https://secretsmanager.eu-west-1.amazonaws.com',
98+
};
99+
100+
await test.step('navigate to secret detail page', async () => {
101+
await secretsPom.toIndex(page);
102+
await secretsPom.isIndexPage(page);
103+
104+
const row = page.locator('tr').filter({ hasText: createdSecretId });
105+
await row.getByRole('button', { name: 'View' }).click();
106+
await secretsPom.isDetailPage(page);
107+
});
108+
109+
await test.step('enter edit mode and update fields', async () => {
110+
await page.getByRole('button', { name: 'Edit' }).click();
111+
112+
// Update AWS fields
113+
for (const [label, value] of Object.entries(updatedFields)) {
114+
await page.getByLabel(label).clear();
115+
await page.getByLabel(label).fill(value);
116+
}
117+
});
118+
119+
await test.step('save the changes', async () => {
120+
await page.getByRole('button', { name: 'Save' }).click();
121+
await secretsPom.isDetailPage(page);
122+
});
123+
124+
await test.step('verify secret was updated via UI', async () => {
125+
// Check the actual field values in the detail page
126+
for (const [label, value] of Object.entries(updatedFields)) {
127+
await expect(page.getByLabel(label)).toHaveValue(value);
128+
}
129+
});
130+
});
131+
132+
test('should delete the secret', async ({ page }) => {
133+
await test.step('navigate to detail page and delete', async () => {
134+
await secretsPom.toIndex(page);
135+
await secretsPom.isIndexPage(page);
136+
137+
const row = page.locator('tr').filter({ hasText: createdSecretId });
138+
await row.getByRole('button', { name: 'View' }).click();
139+
await secretsPom.isDetailPage(page);
140+
141+
await page.getByRole('button', { name: 'Delete' }).click();
142+
143+
const deleteDialog = page.getByRole('dialog', { name: 'Delete Secret' });
144+
await expect(deleteDialog).toBeVisible();
145+
await deleteDialog.getByRole('button', { name: 'Delete' }).click();
146+
});
147+
148+
await test.step('verify deletion and redirect', async () => {
149+
await secretsPom.isIndexPage(page);
150+
await expect(page.getByRole('cell', { name: createdSecretId })).toBeHidden();
151+
});
152+
153+
await test.step('verify secret was deleted via API', async () => {
154+
await expect(async () => {
155+
await e2eReq.get(`${API_SECRETS}/${manager}/${createdSecretId}`);
156+
}).rejects.toThrow();
157+
});
158+
});
159+
});
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { secretsPom } from '@e2e/pom/secrets';
18+
import { e2eReq } from '@e2e/utils/req';
19+
import { test } from '@e2e/utils/test';
20+
import { expect } from '@playwright/test';
21+
22+
import { API_SECRETS } from '@/config/constant';
23+
24+
const createdSecretId = 'test-vault-secret-required';
25+
const manager = 'vault';
26+
27+
test.describe('CRUD secret with required fields only (Vault)', () => {
28+
test.describe.configure({ mode: 'serial' });
29+
30+
test.afterAll(async () => {
31+
// cleanup: delete the secret
32+
if (createdSecretId) {
33+
await e2eReq.delete(`${API_SECRETS}/${manager}/${createdSecretId}`).catch((err) => {
34+
// ignore 404 error if secret doesn't exist, rethrow others
35+
if (err.response?.status !== 404 && !err.message.includes('404')) {
36+
throw err;
37+
}
38+
});
39+
}
40+
});
41+
42+
test('should create a secret with required fields', async ({ page }) => {
43+
await test.step('create secret via UI', async () => {
44+
await secretsPom.toIndex(page);
45+
await secretsPom.getAddSecretBtn(page).click();
46+
await secretsPom.isAddPage(page);
47+
48+
await page.getByLabel('ID').fill(createdSecretId);
49+
50+
// Vault is default
51+
await page.getByLabel('URI').fill('http://vault.example.com:8200');
52+
await page.getByLabel('Prefix').fill('/secret/test');
53+
await page.getByLabel('Token').fill('test-vault-token-123');
54+
55+
await secretsPom.getAddBtn(page).click();
56+
});
57+
58+
await test.step('verify secret appears in UI', async () => {
59+
await secretsPom.isIndexPage(page);
60+
const row = page.locator('tr').filter({ hasText: createdSecretId });
61+
await expect(row).toBeVisible();
62+
});
63+
});
64+
65+
test('should read/view the secret details', async ({ page }) => {
66+
await test.step('navigate to secret details page and verify UI', async () => {
67+
await secretsPom.toIndex(page);
68+
await secretsPom.isIndexPage(page);
69+
70+
// Find and click the View button for the created secret
71+
const row = page.locator('tr').filter({ hasText: createdSecretId });
72+
await row.getByRole('button', { name: 'View' }).click();
73+
await secretsPom.isDetailPage(page);
74+
75+
// Assert Vault field values using input selectors
76+
await expect(page.getByLabel('URI')).toHaveValue('http://vault.example.com:8200');
77+
await expect(page.getByLabel('Prefix')).toHaveValue('/secret/test');
78+
await expect(page.getByLabel('Token')).toHaveValue('test-vault-token-123');
79+
});
80+
});
81+
82+
test('should update the secret', async ({ page }) => {
83+
const updatedFields = {
84+
URI: 'http://vault-updated.example.com:8200',
85+
Prefix: '/secret/updated',
86+
Token: 'updated-vault-token-456',
87+
};
88+
89+
await test.step('navigate to secret detail page', async () => {
90+
await secretsPom.toIndex(page);
91+
await secretsPom.isIndexPage(page);
92+
93+
const row = page.locator('tr').filter({ hasText: createdSecretId });
94+
await row.getByRole('button', { name: 'View' }).click();
95+
await secretsPom.isDetailPage(page);
96+
});
97+
98+
await test.step('enter edit mode and update fields', async () => {
99+
await page.getByRole('button', { name: 'Edit' }).click();
100+
101+
// Update Vault fields
102+
for (const [label, value] of Object.entries(updatedFields)) {
103+
await page.getByLabel(label).clear();
104+
await page.getByLabel(label).fill(value);
105+
}
106+
});
107+
108+
await test.step('save the changes', async () => {
109+
await page.getByRole('button', { name: 'Save' }).click();
110+
await secretsPom.isDetailPage(page);
111+
});
112+
113+
await test.step('verify secret was updated via UI', async () => {
114+
for (const [label, value] of Object.entries(updatedFields)) {
115+
await expect(page.getByLabel(label)).toHaveValue(value);
116+
}
117+
});
118+
});
119+
120+
test('should delete the secret', async ({ page }) => {
121+
await test.step('navigate to detail page and delete', async () => {
122+
await secretsPom.toIndex(page);
123+
await secretsPom.isIndexPage(page);
124+
125+
const row = page.locator('tr').filter({ hasText: createdSecretId });
126+
await row.getByRole('button', { name: 'View' }).click();
127+
await secretsPom.isDetailPage(page);
128+
129+
await page.getByRole('button', { name: 'Delete' }).click();
130+
131+
const deleteDialog = page.getByRole('dialog', { name: 'Delete Secret' });
132+
await expect(deleteDialog).toBeVisible();
133+
await deleteDialog.getByRole('button', { name: 'Delete' }).click();
134+
});
135+
136+
await test.step('verify deletion and redirect', async () => {
137+
await secretsPom.isIndexPage(page);
138+
await expect(page.getByRole('cell', { name: createdSecretId })).toBeHidden();
139+
});
140+
141+
await test.step('verify secret was deleted via API', async () => {
142+
await expect(async () => {
143+
await e2eReq.get(`${API_SECRETS}/${manager}/${createdSecretId}`);
144+
}).rejects.toThrow();
145+
});
146+
});
147+
});

0 commit comments

Comments
 (0)