Skip to content
Open
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
62 changes: 62 additions & 0 deletions e2e/pom/global_rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uiGoto } from '@e2e/utils/ui';
import { expect, type Page } from '@playwright/test';

const locator = {
getGlobalRuleNavBtn: (page: Page) =>
page.getByRole('link', { name: 'Global Rules', exact: true }),
getAddGlobalRuleBtn: (page: Page) =>
page.getByRole('button', { name: 'Add Global Rule', exact: true }),
getAddBtn: (page: Page) =>
page.getByRole('button', { name: 'Add', exact: true }),
};

const assert = {
isIndexPage: async (page: Page) => {
await expect(page).toHaveURL((url) =>
url.pathname.endsWith('/global_rules')
);
const title = page.getByRole('heading', { name: 'Global Rules' });
await expect(title).toBeVisible();
},
isAddPage: async (page: Page) => {
await expect(page).toHaveURL((url) =>
url.pathname.endsWith('/global_rules/add')
);
const title = page.getByRole('heading', { name: 'Add Global Rule' });
await expect(title).toBeVisible();
},
isDetailPage: async (page: Page) => {
await expect(page).toHaveURL((url) =>
url.pathname.includes('/global_rules/detail')
);
const title = page.getByRole('heading', { name: 'Global Rule Detail' });
await expect(title).toBeVisible();
},
};

const goto = {
toIndex: (page: Page) => uiGoto(page, '/global_rules'),
toAdd: (page: Page) => uiGoto(page, '/global_rules/add'),
};

export const globalRulePom = {
...locator,
...assert,
...goto,
};
38 changes: 9 additions & 29 deletions e2e/server/apisix_conf.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,22 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

apisix:
node_listen: 9080 # APISIX listening port
node_listen: 9080
enable_ipv6: false
proxy_mode: http&stream
stream_proxy:
tcp:
- 9100
udp:
- 9200

deployment:
admin:
allow_admin: # https://nginx.org/en/docs/http/ngx_http_access_module.html#allow
- 0.0.0.0/0 # We need to restrict ip access rules for security. 0.0.0.0/0 is for test.

allow_admin:
- 0.0.0.0/0
admin_key:
- name: "admin"
- name: admin
key: edd1c9f034335f136f87ad84b625c8f1
role: admin # admin: manage all configuration data

role: admin
etcd:
host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
- "http://etcd:2379" # multiple etcd address
prefix: "/apisix" # apisix configurations prefix
timeout: 30 # 30 seconds
host:
- http://etcd:2379
prefix: /apisix
timeout: 30
137 changes: 137 additions & 0 deletions e2e/tests/global_rules.crud-all-fields.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { globalRulePom } from '@e2e/pom/global_rules';
import { test } from '@e2e/utils/test';
import {
uiFillMonacoEditor,
uiGetMonacoEditor,
uiHasToastMsg,
} from '@e2e/utils/ui';
import { expect } from '@playwright/test';

test('should CRUD global rule with multiple plugins', async ({ page }) => {
let globalRuleId: string;

await test.step('navigate to add global rule page', async () => {
await globalRulePom.toAdd(page);
await globalRulePom.isAddPage(page);
});

await test.step('add global rule with multiple plugins', async () => {
// ID field should be auto-generated
const idInput = page.getByLabel('ID');
await expect(idInput).toBeVisible();
await expect(idInput).not.toHaveValue('');
globalRuleId = await idInput.inputValue();

// Add first plugin - response-rewrite
const selectPluginBtn = page.getByRole('button', {
name: 'Select Plugins',
});
await selectPluginBtn.click();

const dialog = page.getByRole('dialog', { name: 'Select Plugins' });
await expect(dialog).toBeVisible();

const searchInput = dialog.getByPlaceholder('Search');
await searchInput.fill('response-rewrite');

await dialog
.getByTestId('plugin-response-rewrite')
.getByRole('button', { name: 'Add' })
.click();

const pluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
await expect(pluginDialog).toBeVisible();

// Configure response-rewrite with custom configuration using Monaco editor
const pluginEditor = await uiGetMonacoEditor(page, pluginDialog);
await uiFillMonacoEditor(
page,
pluginEditor,
JSON.stringify({
body: 'test response',
headers: {
set: {
'X-Global-Rule': 'test-global-rule',
},
},
})
);

await pluginDialog.getByRole('button', { name: 'Add' }).click();
await expect(pluginDialog).toBeHidden();

// Add second plugin - cors
await selectPluginBtn.click();

const corsDialog = page.getByRole('dialog', { name: 'Select Plugins' });
await expect(corsDialog).toBeVisible();

const corsSearchInput = corsDialog.getByPlaceholder('Search');
await corsSearchInput.fill('cors');

await corsDialog
.getByTestId('plugin-cors')
.getByRole('button', { name: 'Add' })
.click();

const corsPluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
await expect(corsPluginDialog).toBeVisible();

// Submit with simple configuration for cors
const corsEditor = await uiGetMonacoEditor(page, corsPluginDialog);
await uiFillMonacoEditor(page, corsEditor, '{}');

await corsPluginDialog.getByRole('button', { name: 'Add' }).click();
await expect(corsPluginDialog).toBeHidden();

// Submit the form
await globalRulePom.getAddBtn(page).click();

await uiHasToastMsg(page, {
hasText: 'success',
});

await globalRulePom.isDetailPage(page);
});

await test.step('verify global rule with multiple plugins', async () => {
await expect(page).toHaveURL(
(url) => url.pathname.endsWith(`/global_rules/detail/${globalRuleId}`)
);

// Verify we're on the detail page
await globalRulePom.isDetailPage(page);
});

await test.step('delete global rule from detail page', async () => {
await page.getByRole('button', { name: 'Delete' }).click();

await page
.getByRole('dialog', { name: 'Delete Global Rule' })
.getByRole('button', { name: 'Delete' })
.click();

await globalRulePom.isIndexPage(page);

await uiHasToastMsg(page, {
hasText: 'success',
});
});
});
110 changes: 110 additions & 0 deletions e2e/tests/global_rules.crud-required-fields.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { globalRulePom } from '@e2e/pom/global_rules';
import { test } from '@e2e/utils/test';
import {
uiFillMonacoEditor,
uiGetMonacoEditor,
uiHasToastMsg,
} from '@e2e/utils/ui';
import { expect } from '@playwright/test';

test('should CRUD global rule with required fields only', async ({ page }) => {
let globalRuleId: string;

await test.step('navigate to add global rule page', async () => {
await globalRulePom.toAdd(page);
await globalRulePom.isAddPage(page);
});

await test.step('add global rule with plugins only', async () => {
// ID field should be auto-generated
const idInput = page.getByLabel('ID');
await expect(idInput).toBeVisible();
await expect(idInput).not.toHaveValue('');
globalRuleId = await idInput.inputValue();

// Select a plugin - using response-rewrite as it's simple
const selectPluginBtn = page.getByRole('button', {
name: 'Select Plugins',
});
await selectPluginBtn.click();

// Plugin selection dialog should appear
const dialog = page.getByRole('dialog', { name: 'Select Plugins' });
await expect(dialog).toBeVisible();

// Search and add response-rewrite plugin
const searchInput = dialog.getByPlaceholder('Search');
await searchInput.fill('response-rewrite');

// Click Add button for the plugin
await dialog
.getByTestId('plugin-response-rewrite')
.getByRole('button', { name: 'Add' })
.click();

// Plugin dialog should appear
const pluginDialog = page.getByRole('dialog', { name: 'Add Plugin' });
await expect(pluginDialog).toBeVisible();

// Add minimal plugin configuration using Monaco editor
const pluginEditor = await uiGetMonacoEditor(page, pluginDialog);
await uiFillMonacoEditor(page, pluginEditor, '{"body": "test response"}');

// Submit plugin
await pluginDialog.getByRole('button', { name: 'Add' }).click();
await expect(pluginDialog).toBeHidden();

// Submit the form
await globalRulePom.getAddBtn(page).click();

// Should show success message
await uiHasToastMsg(page, {
hasText: 'success',
});

// Should redirect to detail page
await globalRulePom.isDetailPage(page);
});

await test.step('verify global rule was created', async () => {
// Verify we're on the detail page with correct ID
await expect(page).toHaveURL(
(url) => url.pathname.endsWith(`/global_rules/detail/${globalRuleId}`)
);

// Verify we're on the detail page
await globalRulePom.isDetailPage(page);
});

await test.step('delete global rule from detail page', async () => {
await page.getByRole('button', { name: 'Delete' }).click();

await page
.getByRole('dialog', { name: 'Delete Global Rule' })
.getByRole('button', { name: 'Delete' })
.click();

await globalRulePom.isIndexPage(page);

await uiHasToastMsg(page, {
hasText: 'success',
});
});
});
Loading