Skip to content

Commit 9a7661b

Browse files
authored
test: add E2E tests for global rules (#3248)
* test: add E2E tests for global rules (#3088) - Added global_rules POM with navigation and assertions - Added list test with pagination support (11 items) - Added CRUD test with required fields (single plugin) - Added CRUD test with all fields (multiple plugins) - Tests verify create and delete functionality - All 5 tests passing Closes #3088 * chore: add license header to apisix_conf.yml * Restored apisix_conf.yml with explanatory comments Restored comments for clarity on configuration options.
1 parent 4d94801 commit 9a7661b

File tree

4 files changed

+424
-0
lines changed

4 files changed

+424
-0
lines changed

e2e/pom/global_rules.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+
getGlobalRuleNavBtn: (page: Page) =>
22+
page.getByRole('link', { name: 'Global Rules', exact: true }),
23+
getAddGlobalRuleBtn: (page: Page) =>
24+
page.getByRole('button', { name: 'Add Global Rule', 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('/global_rules')
33+
);
34+
const title = page.getByRole('heading', { name: 'Global Rules' });
35+
await expect(title).toBeVisible();
36+
},
37+
isAddPage: async (page: Page) => {
38+
await expect(page).toHaveURL((url) =>
39+
url.pathname.endsWith('/global_rules/add')
40+
);
41+
const title = page.getByRole('heading', { name: 'Add Global Rule' });
42+
await expect(title).toBeVisible();
43+
},
44+
isDetailPage: async (page: Page) => {
45+
await expect(page).toHaveURL((url) =>
46+
url.pathname.includes('/global_rules/detail')
47+
);
48+
const title = page.getByRole('heading', { name: 'Global Rule Detail' });
49+
await expect(title).toBeVisible();
50+
},
51+
};
52+
53+
const goto = {
54+
toIndex: (page: Page) => uiGoto(page, '/global_rules'),
55+
toAdd: (page: Page) => uiGoto(page, '/global_rules/add'),
56+
};
57+
58+
export const globalRulePom = {
59+
...locator,
60+
...assert,
61+
...goto,
62+
};
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
18+
import { globalRulePom } from '@e2e/pom/global_rules';
19+
import { test } from '@e2e/utils/test';
20+
import {
21+
uiFillMonacoEditor,
22+
uiGetMonacoEditor,
23+
uiHasToastMsg,
24+
} from '@e2e/utils/ui';
25+
import { expect } from '@playwright/test';
26+
27+
test('should CRUD global rule with multiple plugins', async ({ page }) => {
28+
let globalRuleId: string;
29+
30+
await test.step('navigate to add global rule page', async () => {
31+
await globalRulePom.toAdd(page);
32+
await globalRulePom.isAddPage(page);
33+
});
34+
35+
await test.step('add global rule with multiple plugins', async () => {
36+
// ID field should be auto-generated
37+
const idInput = page.getByLabel('ID');
38+
await expect(idInput).toBeVisible();
39+
await expect(idInput).not.toHaveValue('');
40+
globalRuleId = await idInput.inputValue();
41+
42+
// Add first plugin - response-rewrite
43+
const selectPluginBtn = page.getByRole('button', {
44+
name: 'Select Plugins',
45+
});
46+
await selectPluginBtn.click();
47+
48+
const dialog = page.getByRole('dialog', { name: 'Select Plugins' });
49+
await expect(dialog).toBeVisible();
50+
51+
const searchInput = dialog.getByPlaceholder('Search');
52+
await searchInput.fill('response-rewrite');
53+
54+
await dialog
55+
.getByTestId('plugin-response-rewrite')
56+
.getByRole('button', { name: 'Add' })
57+
.click();
58+
59+
const pluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
60+
await expect(pluginDialog).toBeVisible();
61+
62+
// Configure response-rewrite with custom configuration using Monaco editor
63+
const pluginEditor = await uiGetMonacoEditor(page, pluginDialog);
64+
await uiFillMonacoEditor(
65+
page,
66+
pluginEditor,
67+
JSON.stringify({
68+
body: 'test response',
69+
headers: {
70+
set: {
71+
'X-Global-Rule': 'test-global-rule',
72+
},
73+
},
74+
})
75+
);
76+
77+
await pluginDialog.getByRole('button', { name: 'Add' }).click();
78+
await expect(pluginDialog).toBeHidden();
79+
80+
// Add second plugin - cors
81+
await selectPluginBtn.click();
82+
83+
const corsDialog = page.getByRole('dialog', { name: 'Select Plugins' });
84+
await expect(corsDialog).toBeVisible();
85+
86+
const corsSearchInput = corsDialog.getByPlaceholder('Search');
87+
await corsSearchInput.fill('cors');
88+
89+
await corsDialog
90+
.getByTestId('plugin-cors')
91+
.getByRole('button', { name: 'Add' })
92+
.click();
93+
94+
const corsPluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
95+
await expect(corsPluginDialog).toBeVisible();
96+
97+
// Submit with simple configuration for cors
98+
const corsEditor = await uiGetMonacoEditor(page, corsPluginDialog);
99+
await uiFillMonacoEditor(page, corsEditor, '{}');
100+
101+
await corsPluginDialog.getByRole('button', { name: 'Add' }).click();
102+
await expect(corsPluginDialog).toBeHidden();
103+
104+
// Submit the form
105+
await globalRulePom.getAddBtn(page).click();
106+
107+
await uiHasToastMsg(page, {
108+
hasText: 'success',
109+
});
110+
111+
await globalRulePom.isDetailPage(page);
112+
});
113+
114+
await test.step('verify global rule with multiple plugins', async () => {
115+
await expect(page).toHaveURL(
116+
(url) => url.pathname.endsWith(`/global_rules/detail/${globalRuleId}`)
117+
);
118+
119+
// Verify we're on the detail page
120+
await globalRulePom.isDetailPage(page);
121+
});
122+
123+
await test.step('delete global rule from detail page', async () => {
124+
await page.getByRole('button', { name: 'Delete' }).click();
125+
126+
await page
127+
.getByRole('dialog', { name: 'Delete Global Rule' })
128+
.getByRole('button', { name: 'Delete' })
129+
.click();
130+
131+
await globalRulePom.isIndexPage(page);
132+
133+
await uiHasToastMsg(page, {
134+
hasText: 'success',
135+
});
136+
});
137+
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
18+
import { globalRulePom } from '@e2e/pom/global_rules';
19+
import { test } from '@e2e/utils/test';
20+
import {
21+
uiFillMonacoEditor,
22+
uiGetMonacoEditor,
23+
uiHasToastMsg,
24+
} from '@e2e/utils/ui';
25+
import { expect } from '@playwright/test';
26+
27+
test('should CRUD global rule with required fields only', async ({ page }) => {
28+
let globalRuleId: string;
29+
30+
await test.step('navigate to add global rule page', async () => {
31+
await globalRulePom.toAdd(page);
32+
await globalRulePom.isAddPage(page);
33+
});
34+
35+
await test.step('add global rule with plugins only', async () => {
36+
// ID field should be auto-generated
37+
const idInput = page.getByLabel('ID');
38+
await expect(idInput).toBeVisible();
39+
await expect(idInput).not.toHaveValue('');
40+
globalRuleId = await idInput.inputValue();
41+
42+
// Select a plugin - using response-rewrite as it's simple
43+
const selectPluginBtn = page.getByRole('button', {
44+
name: 'Select Plugins',
45+
});
46+
await selectPluginBtn.click();
47+
48+
// Plugin selection dialog should appear
49+
const dialog = page.getByRole('dialog', { name: 'Select Plugins' });
50+
await expect(dialog).toBeVisible();
51+
52+
// Search and add response-rewrite plugin
53+
const searchInput = dialog.getByPlaceholder('Search');
54+
await searchInput.fill('response-rewrite');
55+
56+
// Click Add button for the plugin
57+
await dialog
58+
.getByTestId('plugin-response-rewrite')
59+
.getByRole('button', { name: 'Add' })
60+
.click();
61+
62+
// Plugin dialog should appear
63+
const pluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
64+
await expect(pluginDialog).toBeVisible();
65+
66+
// Add minimal plugin configuration using Monaco editor
67+
const pluginEditor = await uiGetMonacoEditor(page, pluginDialog);
68+
await uiFillMonacoEditor(page, pluginEditor, '{"body": "test response"}');
69+
70+
// Submit plugin
71+
await pluginDialog.getByRole('button', { name: 'Add' }).click();
72+
await expect(pluginDialog).toBeHidden();
73+
74+
// Submit the form
75+
await globalRulePom.getAddBtn(page).click();
76+
77+
// Should show success message
78+
await uiHasToastMsg(page, {
79+
hasText: 'success',
80+
});
81+
82+
// Should redirect to detail page
83+
await globalRulePom.isDetailPage(page);
84+
});
85+
86+
await test.step('verify global rule was created', async () => {
87+
// Verify we're on the detail page with correct ID
88+
await expect(page).toHaveURL(
89+
(url) => url.pathname.endsWith(`/global_rules/detail/${globalRuleId}`)
90+
);
91+
92+
// Verify we're on the detail page
93+
await globalRulePom.isDetailPage(page);
94+
});
95+
96+
await test.step('delete global rule from detail page', async () => {
97+
await page.getByRole('button', { name: 'Delete' }).click();
98+
99+
await page
100+
.getByRole('dialog', { name: 'Delete Global Rule' })
101+
.getByRole('button', { name: 'Delete' })
102+
.click();
103+
104+
await globalRulePom.isIndexPage(page);
105+
106+
await uiHasToastMsg(page, {
107+
hasText: 'success',
108+
});
109+
});
110+
});

0 commit comments

Comments
 (0)