Skip to content

Commit 20cd07d

Browse files
authored
test: routes in services (#3112)
1 parent 63dd73d commit 20cd07d

File tree

4 files changed

+512
-2
lines changed

4 files changed

+512
-2
lines changed

e2e/pom/services.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ const locator = {
2424
page.getByRole('button', { name: 'Add Service', exact: true }),
2525
getAddBtn: (page: Page) =>
2626
page.getByRole('button', { name: 'Add', exact: true }),
27+
// Service routes locators
28+
getServiceRoutesTab: (page: Page) =>
29+
page.getByRole('tab', { name: 'Routes', exact: true }),
30+
getAddRouteBtn: (page: Page) =>
31+
page.getByRole('button', { name: 'Add Route', exact: true }),
2732
};
2833

2934
const assert = {
@@ -46,11 +51,47 @@ const assert = {
4651
const title = page.getByRole('heading', { name: 'Service Detail' });
4752
await expect(title).toBeVisible();
4853
},
54+
// Service routes assertions
55+
isServiceRoutesPage: async (page: Page) => {
56+
await expect(page).toHaveURL(
57+
(url) =>
58+
url.pathname.includes('/services/detail') &&
59+
url.pathname.includes('/routes')
60+
);
61+
// Wait for page to load completely
62+
await page.waitForLoadState('networkidle');
63+
const title = page.getByRole('heading', { name: 'Routes' });
64+
await expect(title).toBeVisible();
65+
},
66+
isServiceRouteAddPage: async (page: Page) => {
67+
await expect(page).toHaveURL(
68+
(url) =>
69+
url.pathname.includes('/services/detail') &&
70+
url.pathname.includes('/routes/add')
71+
);
72+
const title = page.getByRole('heading', { name: 'Add Route' });
73+
await expect(title).toBeVisible();
74+
},
75+
isServiceRouteDetailPage: async (page: Page) => {
76+
await expect(page).toHaveURL(
77+
(url) =>
78+
url.pathname.includes('/services/detail') &&
79+
url.pathname.includes('/routes/detail')
80+
);
81+
const title = page.getByRole('heading', { name: 'Route Detail' });
82+
await expect(title).toBeVisible();
83+
},
4984
};
5085

5186
const goto = {
5287
toIndex: (page: Page) => uiGoto(page, '/services'),
5388
toAdd: (page: Page) => uiGoto(page, '/services/add'),
89+
toServiceRoutes: (page: Page, serviceId: string) =>
90+
uiGoto(page, '/services/detail/$id/routes', { id: serviceId }),
91+
toServiceRouteAdd: (page: Page, serviceId: string) =>
92+
uiGoto(page, '/services/detail/$id/routes/add', {
93+
id: serviceId,
94+
}),
5495
};
5596

5697
export const servicesPom = {
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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 { servicesPom } from '@e2e/pom/services';
18+
import { randomId } from '@e2e/utils/common';
19+
import { e2eReq } from '@e2e/utils/req';
20+
import { test } from '@e2e/utils/test';
21+
import { uiHasToastMsg } from '@e2e/utils/ui';
22+
import { uiFillUpstreamRequiredFields } from '@e2e/utils/ui/upstreams';
23+
import { expect } from '@playwright/test';
24+
25+
import { deleteAllRoutes } from '@/apis/routes';
26+
import { deleteAllServices, postServiceReq } from '@/apis/services';
27+
import type { APISIXType } from '@/types/schema/apisix';
28+
29+
const serviceName = randomId('test-service');
30+
const routeName = randomId('test-route');
31+
const routeUri = '/test-route';
32+
const nodes: APISIXType['UpstreamNode'][] = [
33+
{ host: 'test.com', port: 80, weight: 100 },
34+
{ host: 'test2.com', port: 80, weight: 100 },
35+
];
36+
37+
let testServiceId: string;
38+
39+
test.beforeAll(async () => {
40+
await deleteAllRoutes(e2eReq);
41+
await deleteAllServices(e2eReq);
42+
43+
// Create a test service for testing service routes
44+
const serviceResponse = await postServiceReq(e2eReq, {
45+
name: serviceName,
46+
desc: 'Test service for route testing',
47+
});
48+
49+
testServiceId = serviceResponse.data.value.id;
50+
});
51+
52+
test.afterAll(async () => {
53+
await deleteAllRoutes(e2eReq);
54+
await deleteAllServices(e2eReq);
55+
});
56+
57+
test('should CRUD route under service with required fields', async ({
58+
page,
59+
}) => {
60+
// Navigate to service detail page
61+
await servicesPom.toIndex(page);
62+
await servicesPom.isIndexPage(page);
63+
64+
// Click on the service to go to detail page
65+
await page
66+
.getByRole('row', { name: serviceName })
67+
.getByRole('button', { name: 'View' })
68+
.click();
69+
await servicesPom.isDetailPage(page);
70+
71+
// Navigate to Routes tab
72+
await servicesPom.getServiceRoutesTab(page).click();
73+
await servicesPom.isServiceRoutesPage(page);
74+
75+
await servicesPom.getAddRouteBtn(page).click();
76+
await servicesPom.isServiceRouteAddPage(page);
77+
78+
await test.step('cannot submit without required fields', async () => {
79+
await servicesPom.getAddBtn(page).click();
80+
await servicesPom.isServiceRouteAddPage(page);
81+
await uiHasToastMsg(page, {
82+
hasText: 'invalid configuration',
83+
});
84+
});
85+
86+
await test.step('submit with required fields', async () => {
87+
// Fill in the Name field
88+
await page.getByLabel('Name', { exact: true }).first().fill(routeName);
89+
await page.getByLabel('URI', { exact: true }).fill(routeUri);
90+
91+
// Select HTTP method
92+
await page.getByRole('textbox', { name: 'HTTP Methods' }).click();
93+
await page.getByRole('option', { name: 'GET' }).click();
94+
95+
// Verify service_id is pre-filled and disabled (since it's read-only in service context)
96+
const serviceIdField = page.getByLabel('Service ID', { exact: true });
97+
await expect(serviceIdField).toHaveValue(testServiceId);
98+
await expect(serviceIdField).toBeDisabled();
99+
100+
// Add upstream nodes
101+
const upstreamSection = page.getByRole('group', {
102+
name: 'Upstream',
103+
exact: true,
104+
});
105+
await uiFillUpstreamRequiredFields(upstreamSection, {
106+
nodes,
107+
name: 'test-upstream',
108+
desc: 'test',
109+
});
110+
111+
// Submit the form
112+
await servicesPom.getAddBtn(page).click();
113+
await uiHasToastMsg(page, {
114+
hasText: 'Add Route Successfully',
115+
});
116+
});
117+
118+
await test.step('auto navigate to route detail page', async () => {
119+
await servicesPom.isServiceRouteDetailPage(page);
120+
121+
// Verify the route details
122+
// Verify ID exists
123+
const ID = page.getByRole('textbox', { name: 'ID', exact: true });
124+
await expect(ID).toBeVisible();
125+
await expect(ID).toBeDisabled();
126+
127+
// Verify the route name
128+
const name = page.getByLabel('Name', { exact: true }).first();
129+
await expect(name).toHaveValue(routeName);
130+
await expect(name).toBeDisabled();
131+
132+
// Verify the route URI
133+
const uri = page.getByLabel('URI', { exact: true });
134+
await expect(uri).toHaveValue(routeUri);
135+
await expect(uri).toBeDisabled();
136+
137+
// Verify service_id is still pre-filled and disabled
138+
const serviceIdField = page.getByLabel('Service ID', { exact: true });
139+
await expect(serviceIdField).toHaveValue(testServiceId);
140+
await expect(serviceIdField).toBeDisabled();
141+
});
142+
143+
await test.step('edit and update route in detail page', async () => {
144+
// Click the Edit button in the detail page
145+
await page.getByRole('button', { name: 'Edit' }).click();
146+
147+
// Verify we're in edit mode - fields should be editable now
148+
const nameField = page.getByLabel('Name', { exact: true }).first();
149+
await expect(nameField).toBeEnabled();
150+
151+
// Service ID should still be disabled even in edit mode
152+
const serviceIdField = page.getByLabel('Service ID', { exact: true });
153+
await expect(serviceIdField).toBeDisabled();
154+
155+
// Update the description field
156+
const descriptionField = page.getByLabel('Description').first();
157+
await descriptionField.fill('Updated description for testing');
158+
159+
// Update URI
160+
const uriField = page.getByLabel('URI', { exact: true });
161+
await uriField.fill(`${routeUri}-updated`);
162+
163+
// Click the Save button to save changes
164+
const saveBtn = page.getByRole('button', { name: 'Save' });
165+
await saveBtn.click();
166+
167+
// Verify the update was successful
168+
await uiHasToastMsg(page, {
169+
hasText: 'success',
170+
});
171+
172+
// Verify we're back in detail view mode
173+
await servicesPom.isServiceRouteDetailPage(page);
174+
175+
// Verify the updated fields
176+
// Verify description
177+
await expect(page.getByLabel('Description').first()).toHaveValue(
178+
'Updated description for testing'
179+
);
180+
181+
// Check if the updated URI is visible
182+
await expect(page.getByLabel('URI', { exact: true })).toHaveValue(
183+
`${routeUri}-updated`
184+
);
185+
});
186+
187+
await test.step('route should exist in service routes list', async () => {
188+
// Navigate back to service routes list
189+
await servicesPom.toServiceRoutes(page, testServiceId);
190+
await servicesPom.isServiceRoutesPage(page);
191+
192+
await expect(page.getByRole('cell', { name: routeName })).toBeVisible();
193+
194+
// Click on the route name to go to the detail page
195+
await page
196+
.getByRole('row', { name: routeName })
197+
.getByRole('button', { name: 'View' })
198+
.click();
199+
await servicesPom.isServiceRouteDetailPage(page);
200+
});
201+
202+
await test.step('delete route in detail page', async () => {
203+
// We're already on the detail page from the previous step
204+
205+
// Delete the route
206+
await page.getByRole('button', { name: 'Delete' }).click();
207+
208+
await page
209+
.getByRole('dialog', { name: 'Delete Route' })
210+
.getByRole('button', { name: 'Delete' })
211+
.click();
212+
213+
// Will redirect to service routes page
214+
await servicesPom.isServiceRoutesPage(page);
215+
await uiHasToastMsg(page, {
216+
hasText: 'Delete Route Successfully',
217+
});
218+
await expect(page.getByRole('cell', { name: routeName })).toBeHidden();
219+
});
220+
});

0 commit comments

Comments
 (0)