Skip to content

Commit 4265f44

Browse files
HusneShabbirHusneShabbir
andauthored
Add Playwright Tests for Sidebar, Configurable Prompts, and File Attachment Validation (#874)
* added playwright tests extending testing features * modified locators & test name * Refactored test structure * added locatorUtils * refactored testHelper file * resolved merge confilicts * added extra timeout * changed locator of expect statement * removed visibility statement * change in loc * reverting back to base loc strategies * added locators for UI modifications * modified locators for UI modifications --------- Co-authored-by: HusneShabbir <husneshabbir447@gmail.com>
1 parent 97a3764 commit 4265f44

4 files changed

Lines changed: 207 additions & 6 deletions

File tree

workspaces/lightspeed/packages/app/e2e-tests/lightspeed.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ import {
2626
moreConversations,
2727
} from './fixtures/responses';
2828
import { openLightspeed, sendMessage } from './utils/testHelper';
29+
import {
30+
uploadFile,
31+
validateSuccessfulUpload,
32+
validateFailedUpload,
33+
supportedFileTypes,
34+
} from './utils/fileUpload';
35+
import {
36+
assertChatDialogInitialState,
37+
closeChatDrawer,
38+
openChatDrawer,
39+
assertDrawerState,
40+
} from './utils/sidebar';
2941

3042
const botQuery = 'Please respond';
3143

@@ -79,6 +91,60 @@ test('Models are available', async ({ page }) => {
7991
await expect(dropdown).toHaveText(model);
8092
});
8193

94+
test('Verify sidebar: initial state, close and reopen', async ({ page }) => {
95+
await test.step('Verify initial state of sidebar', async () => {
96+
await assertChatDialogInitialState(page);
97+
});
98+
99+
await test.step('Close the sidebar and verify elements are hidden', async () => {
100+
await closeChatDrawer(page);
101+
await assertDrawerState(page, 'closed');
102+
});
103+
104+
await test.step('Reopen the sidebar and verify elements are visible again', async () => {
105+
await openChatDrawer(page);
106+
await assertDrawerState(page, 'open');
107+
});
108+
});
109+
110+
test('verify default prompts are visible', async ({ page }) => {
111+
await expect(page.getByLabel('Scrollable message log')).toMatchAriaSnapshot(`
112+
- heading "Hello, Guest How can I help you today?" [level=1]
113+
- button
114+
- text: ''
115+
- button
116+
- text: ''
117+
- button
118+
- text: ''
119+
`);
120+
const messageLog = page.locator('div.pf-v6-c-card__title-text');
121+
const textContents = await messageLog.allTextContents();
122+
123+
const nonEmptyTexts = textContents.filter(text => text.trim().length > 0);
124+
125+
expect(nonEmptyTexts.length).toBe(3);
126+
});
127+
128+
test.describe('File Attachment Validation', () => {
129+
const testFiles = [
130+
{ path: '../../package.json', name: 'package.json' },
131+
{ path: __filename, name: 'fileAttachment.spec.ts' },
132+
];
133+
134+
for (const { path, name } of testFiles) {
135+
test(`should validate file: ${name}`, async ({ page }) => {
136+
const fileExtension = `.${name.split('.').pop()}`;
137+
await uploadFile(page, path);
138+
139+
if (supportedFileTypes.includes(fileExtension)) {
140+
await validateSuccessfulUpload(page, name);
141+
} else {
142+
await validateFailedUpload(page);
143+
}
144+
});
145+
}
146+
});
147+
82148
test.describe('Conversation', () => {
83149
test.beforeEach(async ({ page }) => {
84150
await page.route(`${modelBaseUrl}/conversations`, async route => {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Page, Locator, FileChooser, expect } from '@playwright/test';
17+
18+
export const supportedFileTypes = ['.txt', '.yaml', '.json'];
19+
20+
export async function triggerFileChooser(
21+
page: Page,
22+
buttonLocator: Locator,
23+
): Promise<FileChooser> {
24+
const [fileChooser] = await Promise.all([
25+
page.waitForEvent('filechooser'),
26+
buttonLocator.click(),
27+
]);
28+
return fileChooser;
29+
}
30+
31+
export async function uploadFile(page: Page, filePath: string) {
32+
const attachButton = page.getByRole('button', { name: 'Attach' });
33+
await expect(attachButton).toBeVisible();
34+
35+
const fileChooser = await triggerFileChooser(page, attachButton);
36+
await fileChooser.setFiles(filePath);
37+
}
38+
39+
export async function validateSuccessfulUpload(page: Page, fileName: string) {
40+
const trimmerFilename = fileName.split('.')[0];
41+
42+
await expect(page.getByRole('button', { name: fileName })).toBeVisible();
43+
44+
const spanWithText = page
45+
.locator('span', { hasText: trimmerFilename })
46+
.first();
47+
await spanWithText.click();
48+
49+
const jsonStarter = page.locator('div', { hasText: /^\{$/ }).first();
50+
await jsonStarter.waitFor();
51+
52+
await expect(page.getByRole('banner')).toContainText('Preview attachment');
53+
await expect(page.getByRole('button', { name: 'Edit' })).toBeVisible();
54+
await expect(
55+
page.getByRole('contentinfo').getByRole('button', { name: 'Close' }),
56+
).toBeVisible();
57+
58+
await page.getByRole('button', { name: 'Edit' }).click();
59+
60+
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
61+
await expect(page.getByRole('button', { name: 'Cancel' })).toBeVisible();
62+
63+
await page.getByRole('button', { name: 'Save' }).click();
64+
await page
65+
.getByRole('contentinfo')
66+
.getByRole('button', { name: 'Close' })
67+
.click();
68+
await page
69+
.getByRole('button', { name: `Close ${trimmerFilename}` })
70+
.click({ force: true });
71+
}
72+
73+
export async function validateFailedUpload(page: Page) {
74+
const alertHeader = page.getByText('File upload failed');
75+
const alertText = page.getByText(
76+
'Unsupported file type. Supported types are: .txt, .yaml, .json.',
77+
);
78+
79+
await expect(alertHeader).toBeVisible();
80+
await expect(alertText).toBeVisible();
81+
82+
await page.getByRole('button', { name: 'Close Danger alert' }).click();
83+
await expect(alertHeader).toBeHidden();
84+
await expect(alertText).toBeHidden();
85+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Page, expect, Locator } from '@playwright/test';
17+
18+
export async function assertChatDialogInitialState(page: Page) {
19+
await expect(page.getByLabel('Chatbot', { exact: true })).toContainText(
20+
'Developer Hub Lightspeed',
21+
);
22+
await expect(page.getByRole('button', { name: 'Toggle menu' })).toBeVisible();
23+
await assertDrawerState(page, 'open');
24+
}
25+
26+
export async function closeChatDrawer(page: Page) {
27+
const closeButton = page.getByRole('button', { name: 'Close drawer panel' });
28+
await closeButton.click();
29+
}
30+
31+
export async function openChatDrawer(page: Page) {
32+
const toggleButton = page.getByRole('button', { name: 'Toggle menu' });
33+
await toggleButton.click();
34+
}
35+
36+
export async function assertDrawerState(page: Page, state: 'open' | 'closed') {
37+
const expectations = {
38+
open: (locator: Locator) => expect(locator).toBeVisible(),
39+
closed: (locator: Locator) => expect(locator).toBeHidden(),
40+
};
41+
42+
const checks = [
43+
page.getByRole('button', { name: 'Close drawer panel' }),
44+
page.getByRole('textbox', { name: 'Filter menu items' }),
45+
page.getByRole('separator', { name: 'Resize' }),
46+
];
47+
48+
for (const locator of checks) {
49+
await expectations[state](locator);
50+
}
51+
}

workspaces/lightspeed/packages/app/e2e-tests/utils/testHelper.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,18 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { Page } from 'playwright';
17+
import { Page } from '@playwright/test';
1818

1919
export const openLightspeed = async (page: Page) => {
20-
const navLink = page.locator(`nav a:has-text("lightspeed")`).first();
21-
await navLink.waitFor({ state: 'visible' });
20+
const navLink = page.getByRole('link', { name: 'Lightspeed' });
2221
await navLink.click();
2322

2423
await page.locator('.pf-chatbot__messagebox').waitFor({ state: 'visible' });
2524
};
2625

2726
export const sendMessage = async (message: string, page: Page) => {
28-
const inputLocator = page.getByRole('textbox').first();
29-
await inputLocator.waitFor({ state: 'visible' });
27+
const inputLocator = page.getByRole('textbox', { name: 'Send a message...' });
3028
await inputLocator.fill(message);
31-
await page.locator('button[aria-label="Send"]').click();
29+
const sendButton = page.getByRole('button', { name: 'Send' });
30+
await sendButton.click();
3231
};

0 commit comments

Comments
 (0)