Skip to content

Commit 41c0ef5

Browse files
committed
feat(e2e): add E2E tests for Consumers resource
- Add Page Object Model for Consumers (e2e/pom/consumers.ts) - Add list and pagination tests (consumers.list.spec.ts) - Add CRUD tests with required fields (consumers.crud-required-fields.spec.ts) - Add CRUD tests with all fields (consumers.crud-all-fields.spec.ts) - Add deleteAllConsumers() helper function in src/apis/consumers.ts All tests follow the established pattern: - *.list.spec.ts for list page and pagination - *.crud-required-fields.spec.ts for basic CRUD operations - *.crud-all-fields.spec.ts for comprehensive CRUD with all fields Tests cover: - Navigation and page assertions - Form validation - Create, Read, Update, Delete operations - Labels management with tags input - Pagination with table controls and URL params Fixes username validation by using only allowed characters [a-zA-Z0-9_-]
1 parent 62850cd commit 41c0ef5

File tree

5 files changed

+430
-1
lines changed

5 files changed

+430
-1
lines changed

e2e/pom/consumers.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
getConsumerNavBtn: (page: Page) =>
22+
page.getByRole('link', { name: 'Consumers', exact: true }),
23+
getAddConsumerBtn: (page: Page) =>
24+
page.getByRole('button', { name: 'Add Consumer', 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) => url.pathname.endsWith('/consumers'));
32+
const title = page.getByRole('heading', { name: 'Consumers' });
33+
await expect(title).toBeVisible();
34+
},
35+
isAddPage: async (page: Page) => {
36+
await expect(page).toHaveURL((url) => url.pathname.endsWith('/consumers/add'));
37+
const title = page.getByRole('heading', { name: 'Add Consumer' });
38+
await expect(title).toBeVisible();
39+
},
40+
isDetailPage: async (page: Page) => {
41+
await expect(page).toHaveURL((url) =>
42+
url.pathname.includes('/consumers/detail')
43+
);
44+
const title = page.getByRole('heading', { name: 'Consumer Detail' });
45+
await expect(title).toBeVisible();
46+
},
47+
};
48+
49+
const goto = {
50+
toIndex: (page: Page) => uiGoto(page, '/consumers'),
51+
toAdd: (page: Page) => uiGoto(page, '/consumers/add'),
52+
};
53+
54+
export const consumersPom = {
55+
...locator,
56+
...assert,
57+
...goto,
58+
};
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 { consumersPom } from '@e2e/pom/consumers';
18+
import { e2eReq } from '@e2e/utils/req';
19+
import { test } from '@e2e/utils/test';
20+
import { uiHasToastMsg } from '@e2e/utils/ui';
21+
import { expect } from '@playwright/test';
22+
import { customAlphabet } from 'nanoid';
23+
24+
import { deleteAllConsumers } from '@/apis/consumers';
25+
26+
// Consumer usernames can only contain: a-zA-Z0-9_-
27+
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 10);
28+
const consumerUsername = `testconsumer${nanoid()}`;
29+
const description = 'Test consumer with all fields filled';
30+
31+
test.beforeAll(async () => {
32+
await deleteAllConsumers(e2eReq);
33+
});
34+
35+
test('should CRUD consumer with all fields', async ({ page }) => {
36+
test.slow();
37+
38+
await consumersPom.toIndex(page);
39+
await consumersPom.isIndexPage(page);
40+
41+
await consumersPom.getAddConsumerBtn(page).click();
42+
await consumersPom.isAddPage(page);
43+
44+
await test.step('submit with all fields', async () => {
45+
// Fill username (required)
46+
await page.getByRole('textbox', { name: 'Username' }).fill(consumerUsername);
47+
48+
// Fill description (optional)
49+
await page.getByRole('textbox', { name: 'Description' }).fill(description);
50+
51+
// Add labels using tags input
52+
const labelsInput = page.getByPlaceholder('Input text like `key:value`, then enter or blur');
53+
await labelsInput.fill('version:v1');
54+
await labelsInput.press('Enter');
55+
await labelsInput.fill('env:test');
56+
await labelsInput.press('Enter');
57+
await labelsInput.fill('team:engineering');
58+
await labelsInput.press('Enter');
59+
60+
// Submit the form
61+
await consumersPom.getAddBtn(page).click();
62+
await uiHasToastMsg(page, {
63+
hasText: 'Add Consumer Successfully',
64+
});
65+
});
66+
67+
await test.step('auto navigate to consumer detail page', async () => {
68+
await consumersPom.isDetailPage(page);
69+
70+
// Verify the consumer username
71+
await expect(page.getByRole('textbox', { name: 'Username' }))
72+
.toHaveValue(consumerUsername);
73+
});
74+
75+
await test.step('edit and update all fields', async () => {
76+
// Enter edit mode
77+
await page.getByRole('button', { name: 'Edit' }).click();
78+
79+
// Update description
80+
await page.getByRole('textbox', { name: 'Description' }).fill('Updated: ' + description);
81+
82+
// Update labels - remove old ones and add new ones
83+
// First, remove existing labels by clicking the X button
84+
const labelsSection = page.getByRole('group', { name: 'Basic Infomation' });
85+
const removeButtons = labelsSection.locator('button[aria-label^="Remove"]');
86+
const count = await removeButtons.count();
87+
for (let i = 0; i < count; i++) {
88+
await removeButtons.first().click();
89+
}
90+
91+
// Add new labels
92+
const labelsInput = page.getByPlaceholder('Input text like `key:value`, then enter or blur');
93+
await labelsInput.fill('version:v2');
94+
await labelsInput.press('Enter');
95+
await labelsInput.fill('env:production');
96+
await labelsInput.press('Enter');
97+
await labelsInput.fill('team:platform');
98+
await labelsInput.press('Enter');
99+
100+
// Save changes
101+
await page.getByRole('button', { name: 'Save' }).click();
102+
await uiHasToastMsg(page, {
103+
hasText: 'success',
104+
});
105+
106+
// Verify updates
107+
await expect(page.getByRole('textbox', { name: 'Description' }))
108+
.toHaveValue('Updated: ' + description);
109+
});
110+
111+
await test.step('verify consumer in list page', async () => {
112+
await consumersPom.getConsumerNavBtn(page).click();
113+
await consumersPom.isIndexPage(page);
114+
115+
// Find the consumer in the list
116+
const row = page.getByRole('row', { name: consumerUsername });
117+
await expect(row).toBeVisible();
118+
});
119+
120+
await test.step('delete consumer', async () => {
121+
// Navigate to detail page
122+
await page
123+
.getByRole('row', { name: consumerUsername })
124+
.getByRole('button', { name: 'View' })
125+
.click();
126+
await consumersPom.isDetailPage(page);
127+
128+
// Delete
129+
await page.getByRole('button', { name: 'Delete' }).click();
130+
await page
131+
.getByRole('dialog', { name: 'Delete Consumer' })
132+
.getByRole('button', { name: 'Delete' })
133+
.click();
134+
135+
// Verify deletion
136+
await uiHasToastMsg(page, {
137+
hasText: 'Delete Consumer Successfully',
138+
});
139+
140+
// Navigate to consumers list to verify consumer is gone
141+
await consumersPom.toIndex(page);
142+
await consumersPom.isIndexPage(page);
143+
await expect(page.getByRole('cell', { name: consumerUsername })).toBeHidden();
144+
});
145+
});
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 { consumersPom } from '@e2e/pom/consumers';
18+
import { e2eReq } from '@e2e/utils/req';
19+
import { test } from '@e2e/utils/test';
20+
import { uiHasToastMsg } from '@e2e/utils/ui';
21+
import { expect } from '@playwright/test';
22+
import { customAlphabet } from 'nanoid';
23+
24+
import { deleteAllConsumers } from '@/apis/consumers';
25+
26+
// Consumer usernames can only contain: a-zA-Z0-9_-
27+
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 10);
28+
const consumerUsername = `testconsumer${nanoid()}`;
29+
30+
test.beforeAll(async () => {
31+
await deleteAllConsumers(e2eReq);
32+
});
33+
34+
test('should CRUD consumer with required fields', async ({ page }) => {
35+
await consumersPom.toIndex(page);
36+
await consumersPom.isIndexPage(page);
37+
38+
await consumersPom.getAddConsumerBtn(page).click();
39+
await consumersPom.isAddPage(page);
40+
41+
await test.step('cannot submit without required fields', async () => {
42+
await consumersPom.getAddBtn(page).click();
43+
// Should stay on add page - form validation prevents submission
44+
await consumersPom.isAddPage(page);
45+
});
46+
47+
await test.step('submit with required fields', async () => {
48+
// Fill in the Username field (only required field for consumers)
49+
await page.getByRole('textbox', { name: 'Username' }).fill(consumerUsername);
50+
51+
// Submit the form
52+
await consumersPom.getAddBtn(page).click();
53+
await uiHasToastMsg(page, {
54+
hasText: 'Add Consumer Successfully',
55+
});
56+
});
57+
58+
await test.step('auto navigate to consumer detail page', async () => {
59+
await consumersPom.isDetailPage(page);
60+
61+
// Verify the consumer username
62+
const username = page.getByRole('textbox', { name: 'Username' });
63+
await expect(username).toHaveValue(consumerUsername);
64+
await expect(username).toBeDisabled();
65+
});
66+
67+
await test.step('edit and update consumer in detail page', async () => {
68+
// Click the Edit button in the detail page
69+
await page.getByRole('button', { name: 'Edit' }).click();
70+
71+
// Update the description field
72+
const descriptionField = page.getByRole('textbox', { name: 'Description' });
73+
await descriptionField.fill('Updated description for testing');
74+
75+
// Click the Save button to save changes
76+
const saveBtn = page.getByRole('button', { name: 'Save' });
77+
await saveBtn.click();
78+
79+
// Verify the update was successful
80+
await uiHasToastMsg(page, {
81+
hasText: 'success',
82+
});
83+
84+
// Verify we're back in detail view mode
85+
await consumersPom.isDetailPage(page);
86+
87+
// Verify the updated fields
88+
await expect(page.getByRole('textbox', { name: 'Description' })).toHaveValue(
89+
'Updated description for testing'
90+
);
91+
});
92+
93+
await test.step('consumer should exist in list page', async () => {
94+
await consumersPom.getConsumerNavBtn(page).click();
95+
await consumersPom.isIndexPage(page);
96+
await expect(page.getByRole('cell', { name: consumerUsername })).toBeVisible();
97+
98+
// Click on the view button to go to the detail page
99+
await page
100+
.getByRole('row', { name: consumerUsername })
101+
.getByRole('button', { name: 'View' })
102+
.click();
103+
await consumersPom.isDetailPage(page);
104+
});
105+
106+
await test.step('delete consumer in detail page', async () => {
107+
// We're already on the detail page from the previous step
108+
109+
// Delete the consumer
110+
await page.getByRole('button', { name: 'Delete' }).click();
111+
112+
await page
113+
.getByRole('dialog', { name: 'Delete Consumer' })
114+
.getByRole('button', { name: 'Delete' })
115+
.click();
116+
117+
// Verify deletion was successful with toast
118+
await uiHasToastMsg(page, {
119+
hasText: 'Delete Consumer Successfully',
120+
});
121+
122+
// Navigate to consumers index to verify consumer is gone
123+
await consumersPom.toIndex(page);
124+
await consumersPom.isIndexPage(page);
125+
await expect(page.getByRole('cell', { name: consumerUsername })).toBeHidden();
126+
});
127+
});

0 commit comments

Comments
 (0)