Skip to content

Commit e611715

Browse files
authored
test(plugin_configs): add comprehensive E2E tests for plugin configs resource (#3246)
- Add Page Object Model for plugin configs navigation and assertions - Add list page tests with pagination support - Add CRUD tests with required fields (name, plugins) - Add CRUD tests with all fields (name, desc, labels, plugins) - Tests cover create, read, update, delete operations - Tests verify form validation, plugin management, and labels Closes #3090
1 parent 9a7661b commit e611715

File tree

4 files changed

+645
-0
lines changed

4 files changed

+645
-0
lines changed

e2e/pom/plugin_configs.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
getPluginConfigNavBtn: (page: Page) =>
22+
page.getByRole('link', { name: 'Plugin Configs', exact: true }),
23+
getAddPluginConfigBtn: (page: Page) =>
24+
page.getByRole('button', { name: 'Add Plugin Config', exact: true }),
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) =>
32+
url.pathname.endsWith('/plugin_configs')
33+
);
34+
const title = page.getByRole('heading', { name: 'Plugin Configs' });
35+
await expect(title).toBeVisible();
36+
},
37+
isAddPage: async (page: Page) => {
38+
await expect(page).toHaveURL((url) =>
39+
url.pathname.endsWith('/plugin_configs/add')
40+
);
41+
const title = page.getByRole('heading', { name: 'Add Plugin Config' });
42+
await expect(title).toBeVisible();
43+
},
44+
isDetailPage: async (page: Page) => {
45+
await expect(page).toHaveURL((url) =>
46+
url.pathname.includes('/plugin_configs/detail')
47+
);
48+
const title = page.getByRole('heading', { name: 'Plugin Config Detail' });
49+
await expect(title).toBeVisible();
50+
},
51+
};
52+
53+
const goto = {
54+
toIndex: (page: Page) => uiGoto(page, '/plugin_configs'),
55+
toAdd: (page: Page) => uiGoto(page, '/plugin_configs/add'),
56+
};
57+
58+
export const pluginConfigsPom = {
59+
...locator,
60+
...assert,
61+
...goto,
62+
};
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
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 { pluginConfigsPom } from '@e2e/pom/plugin_configs';
18+
import { randomId } from '@e2e/utils/common';
19+
import { e2eReq } from '@e2e/utils/req';
20+
import { test } from '@e2e/utils/test';
21+
import {
22+
uiFillMonacoEditor,
23+
uiGetMonacoEditor,
24+
uiHasToastMsg,
25+
} from '@e2e/utils/ui';
26+
import { expect } from '@playwright/test';
27+
28+
import { API_PLUGIN_CONFIGS } from '@/config/constant';
29+
import type { APISIXType } from '@/types/schema/apisix';
30+
31+
// Helper function to delete all plugin configs
32+
const deleteAllPluginConfigs = async (req: typeof e2eReq) => {
33+
const response = await req.get<unknown, APISIXType['RespPluginConfigList']>(
34+
API_PLUGIN_CONFIGS
35+
);
36+
const list = response.data.list || [];
37+
await Promise.all(
38+
list.map((item) => req.delete(`${API_PLUGIN_CONFIGS}/${item.value.id}`))
39+
);
40+
};
41+
42+
const pluginConfigNameWithAllFields = randomId('test-plugin-config-full');
43+
const description =
44+
'This is a test description for the plugin config with all fields';
45+
46+
test.beforeAll(async () => {
47+
await deleteAllPluginConfigs(e2eReq);
48+
});
49+
50+
test('should CRUD plugin config with all fields', async ({ page }) => {
51+
test.slow();
52+
53+
// Navigate to the plugin config list page
54+
await pluginConfigsPom.toIndex(page);
55+
await pluginConfigsPom.isIndexPage(page);
56+
57+
// Click the add plugin config button
58+
await pluginConfigsPom.getAddPluginConfigBtn(page).click();
59+
await pluginConfigsPom.isAddPage(page);
60+
61+
await test.step('fill in all fields', async () => {
62+
// Fill in basic fields
63+
await page
64+
.getByLabel('Name', { exact: true })
65+
.first()
66+
.fill(pluginConfigNameWithAllFields);
67+
await page.getByLabel('Description').first().fill(description);
68+
69+
// Add labels using TagsInput
70+
const labelsInput = page.getByPlaceholder('key:value');
71+
await labelsInput.fill('env:production');
72+
await labelsInput.press('Enter');
73+
await labelsInput.fill('version:v1.0');
74+
await labelsInput.press('Enter');
75+
76+
// Add plugins
77+
const selectPluginsBtn = page.getByRole('button', {
78+
name: 'Select Plugins',
79+
});
80+
await selectPluginsBtn.click();
81+
82+
// Add response-rewrite plugin
83+
const selectPluginsDialog = page.getByRole('dialog', {
84+
name: 'Select Plugins',
85+
});
86+
const searchInput = selectPluginsDialog.getByPlaceholder('Search');
87+
await searchInput.fill('response-rewrite');
88+
89+
await selectPluginsDialog
90+
.getByTestId('plugin-response-rewrite')
91+
.getByRole('button', { name: 'Add' })
92+
.click();
93+
94+
const addPluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
95+
const pluginEditor = await uiGetMonacoEditor(page, addPluginDialog);
96+
await uiFillMonacoEditor(
97+
page,
98+
pluginEditor,
99+
'{"body": "test response body", "headers": {"X-Custom-Header": "custom-value"}}'
100+
);
101+
// add plugin
102+
await addPluginDialog.getByRole('button', { name: 'Add' }).click();
103+
await expect(addPluginDialog).toBeHidden();
104+
105+
const pluginsSection = page.getByRole('group', { name: 'Plugins' });
106+
const responseRewritePlugin = pluginsSection.getByTestId(
107+
'plugin-response-rewrite'
108+
);
109+
await responseRewritePlugin.getByRole('button', { name: 'Edit' }).click();
110+
111+
// should show edit plugin dialog
112+
const editPluginDialog = page.getByRole('dialog', { name: 'Edit Plugin' });
113+
114+
await expect(
115+
editPluginDialog.getByText('test response body')
116+
).toBeVisible();
117+
await expect(editPluginDialog.getByText('X-Custom-Header')).toBeVisible();
118+
// save edit plugin dialog
119+
await editPluginDialog.getByRole('button', { name: 'Save' }).click();
120+
await expect(editPluginDialog).toBeHidden();
121+
122+
// Submit the form
123+
await pluginConfigsPom.getAddBtn(page).click();
124+
await uiHasToastMsg(page, {
125+
hasText: 'Add Plugin Config Successfully',
126+
});
127+
});
128+
129+
await test.step('auto navigate to plugin config detail page and verify all fields', async () => {
130+
// After creation, we should be redirected to the plugin configs detail page
131+
await pluginConfigsPom.isDetailPage(page);
132+
133+
// Verify the plugin config details
134+
// Verify ID exists
135+
const ID = page.getByRole('textbox', { name: 'ID', exact: true });
136+
await expect(ID).toBeVisible();
137+
await expect(ID).toBeDisabled();
138+
139+
// Verify the plugin config name
140+
const name = page.getByLabel('Name', { exact: true }).first();
141+
await expect(name).toHaveValue(pluginConfigNameWithAllFields);
142+
await expect(name).toBeDisabled();
143+
144+
// Verify the description
145+
const desc = page.getByLabel('Description').first();
146+
await expect(desc).toHaveValue(description);
147+
await expect(desc).toBeDisabled();
148+
149+
// Verify labels
150+
const labelsInput = page.getByPlaceholder('key:value');
151+
await expect(labelsInput).toBeVisible();
152+
// Verify the pills/tags are visible
153+
await expect(page.getByText('env:production')).toBeVisible();
154+
await expect(page.getByText('version:v1.0')).toBeVisible();
155+
156+
// Verify Plugins
157+
await expect(page.getByTestId('plugin-response-rewrite')).toBeVisible();
158+
});
159+
160+
await test.step('edit and update plugin config in detail page', async () => {
161+
// Click the Edit button in the detail page
162+
await page.getByRole('button', { name: 'Edit' }).click();
163+
164+
// Verify we're in edit mode - fields should be editable now
165+
const nameField = page.getByLabel('Name', { exact: true }).first();
166+
await expect(nameField).toBeEnabled();
167+
168+
// Update the description field
169+
const descriptionField = page.getByLabel('Description').first();
170+
await descriptionField.fill(
171+
'Updated description for testing all fields'
172+
);
173+
174+
// Update labels - just add a new one
175+
const labelsInput = page.getByPlaceholder('key:value');
176+
await labelsInput.fill('team:backend');
177+
await labelsInput.press('Enter');
178+
179+
// Update response-rewrite plugin configuration
180+
const pluginsSection = page.getByRole('group', { name: 'Plugins' });
181+
const responseRewritePlugin = pluginsSection.getByTestId(
182+
'plugin-response-rewrite'
183+
);
184+
await responseRewritePlugin.getByRole('button', { name: 'Edit' }).click();
185+
186+
const editPluginDialog = page.getByRole('dialog', { name: 'Edit Plugin' });
187+
const pluginEditor = await uiGetMonacoEditor(page, editPluginDialog);
188+
await uiFillMonacoEditor(
189+
page,
190+
pluginEditor,
191+
'{"body": "updated response body", "headers": {"X-Updated-Header": "updated-value"}}'
192+
);
193+
await editPluginDialog.getByRole('button', { name: 'Save' }).click();
194+
await expect(editPluginDialog).toBeHidden();
195+
196+
// Click the Save button to save changes
197+
const saveBtn = page.getByRole('button', { name: 'Save' });
198+
await saveBtn.click();
199+
200+
// Verify the update was successful
201+
await uiHasToastMsg(page, {
202+
hasText: 'success',
203+
});
204+
205+
// Verify we're back in detail view mode
206+
await pluginConfigsPom.isDetailPage(page);
207+
208+
// Verify the updated fields
209+
// Verify description
210+
await expect(page.getByLabel('Description').first()).toHaveValue(
211+
'Updated description for testing all fields'
212+
);
213+
214+
// Verify updated labels
215+
await expect(page.getByText('env:production')).toBeVisible();
216+
await expect(page.getByText('version:v1.0')).toBeVisible();
217+
await expect(page.getByText('team:backend')).toBeVisible();
218+
219+
// Verify plugins - response-rewrite should still be there
220+
await expect(page.getByTestId('plugin-response-rewrite')).toBeVisible();
221+
222+
// Return to list page and verify the plugin config exists
223+
await pluginConfigsPom.getPluginConfigNavBtn(page).click();
224+
await pluginConfigsPom.isIndexPage(page);
225+
226+
// Find the row with our plugin config
227+
const row = page.getByRole('row', { name: pluginConfigNameWithAllFields });
228+
await expect(row).toBeVisible();
229+
});
230+
231+
await test.step('delete plugin config in detail page', async () => {
232+
// Navigate to detail page
233+
await page
234+
.getByRole('row', { name: pluginConfigNameWithAllFields })
235+
.getByRole('button', { name: 'View' })
236+
.click();
237+
await pluginConfigsPom.isDetailPage(page);
238+
239+
// Delete the plugin config
240+
await page.getByRole('button', { name: 'Delete' }).click();
241+
242+
await page
243+
.getByRole('dialog', { name: 'Delete Plugin Config' })
244+
.getByRole('button', { name: 'Delete' })
245+
.click();
246+
247+
// Will redirect to plugin configs page
248+
await pluginConfigsPom.isIndexPage(page);
249+
await uiHasToastMsg(page, {
250+
hasText: 'Delete Plugin Config Successfully',
251+
});
252+
await expect(
253+
page.getByRole('cell', { name: pluginConfigNameWithAllFields })
254+
).toBeHidden();
255+
256+
// Final verification: Reload the page and check again to ensure it's really gone
257+
await page.reload();
258+
await pluginConfigsPom.isIndexPage(page);
259+
260+
// After reload, the plugin config should still be gone
261+
await expect(
262+
page.getByRole('cell', { name: pluginConfigNameWithAllFields })
263+
).toBeHidden();
264+
});
265+
});

0 commit comments

Comments
 (0)