Skip to content

Commit 4f4a6ec

Browse files
authored
Merge pull request #240 from FalkorDB/code-graph-ui-tests
Code graph UI tests
2 parents 5ce70a0 + 271e066 commit 4f4a6ec

File tree

8 files changed

+253
-13
lines changed

8 files changed

+253
-13
lines changed

app/components/Input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
144144
<div
145145
ref={containerRef}
146146
className="z-10 w-full bg-white absolute flex flex-col pointer-events-auto border rounded-md max-h-[50dvh] overflow-y-auto overflow-x-hidden p-2 gap-2"
147+
data-name='search-bar-list'
147148
style={{
148149
top: (inputRef.current?.clientHeight || 0) + 16
149150
}}

e2e/config/testData.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const searchData: { searchInput: string; completedSearchInput?: string; }[] = [
2+
{ searchInput: "test"},
3+
{ searchInput: "set"},
4+
{ searchInput: "low", completedSearchInput: "lower" },
5+
{ searchInput: "as", completedSearchInput: "ask"},
6+
];
7+
8+
const categorizeCharacters = (characters: string[], expectedRes: boolean): { character: string; expectedRes: boolean }[] => {
9+
return characters.map(character => ({ character, expectedRes }));
10+
};
11+
12+
export const specialCharacters: { character: string; expectedRes: boolean }[] = [
13+
...categorizeCharacters(['%', '*', '(', ')', '-', '[', ']', '{', '}', ';', ':', '"', '|', '~'], true),
14+
...categorizeCharacters(['!', '@', '#', '$', '^', '&', '_', '=', '+', "'", ',', '.', '<', '>', '/', '?', '\\', '`'], false)
15+
];

e2e/logic/POM/codeGraph.ts

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Locator, Page } from "playwright";
22
import BasePage from "../../infra/ui/basePage";
3-
import { delay } from "../utils";
3+
import { waitToBeEnabled } from "../utils";
44

55
export default class CodeGraph extends BasePage {
66
/* NavBar Locators*/
@@ -33,6 +33,42 @@ export default class CodeGraph extends BasePage {
3333
return this.page.locator("//main[@data-name='main-chat']/*[last()]/p");
3434
}
3535

36+
private get typeUrlInput(): Locator {
37+
return this.page.locator("//div[@role='dialog']/form/input");
38+
}
39+
40+
private get createBtnInCreateProjectDialog(): Locator {
41+
return this.page.locator("//div[@role='dialog']/form//following::button//p[contains(text(), 'Create')]")
42+
}
43+
44+
private get createProjectWaitDialog(): Locator {
45+
return this.page.locator("//div[@role='dialog']//div//h2[contains(text(), 'THANK YOU FOR A NEW REQUEST')]")
46+
}
47+
48+
private get dialogCreatedGraphsList(): (graph: string) => Locator {
49+
return (graph: string) => this.page.locator(`//div[@role='presentation']/div//span[2][contains(text(), '${graph}')]`);
50+
}
51+
52+
private get searchBarInput(): Locator {
53+
return this.page.locator("//div[@data-name='search-bar']/input");
54+
}
55+
56+
private get searchBarAutoCompleteOptions(): Locator {
57+
return this.page.locator("//div[@data-name='search-bar']/div/button");
58+
}
59+
60+
private get searchBarElements(): Locator {
61+
return this.page.locator("//div[@data-name='search-bar']/div/button/div/p[1]");
62+
}
63+
64+
private get searchBarOptionBtn(): (buttonNum: string) => Locator {
65+
return (buttonNum: string) => this.page.locator(`//div[@data-name='search-bar']//button[${buttonNum}]`);
66+
}
67+
68+
private get searchBarList(): Locator {
69+
return this.page.locator("//div[@data-name='search-bar-list']");
70+
}
71+
3672
/* Chat Locators */
3773
private get showPathBtn(): Locator {
3874
return this.page.locator("//button[contains(@class, 'Tip')]");
@@ -74,7 +110,7 @@ export default class CodeGraph extends BasePage {
74110
return (inputNum: string) => this.page.locator(`(//main[@data-name='main-chat']//input)[1]/following::div[${inputNum}]//button[1]`);
75111
}
76112

77-
private get notificationNoPathFound(): Locator {
113+
private get notificationError(): Locator {
78114
return this.page.locator("//div[@role='region']//ol//li");
79115
}
80116

@@ -115,7 +151,7 @@ export default class CodeGraph extends BasePage {
115151
}
116152

117153
async sendMessage(message: string) {
118-
await this.askquestionInput.isEnabled();
154+
await waitToBeEnabled(this.askquestionInput);
119155
await this.askquestionInput.fill(message);
120156
await this.askquestionBtn.click();
121157
}
@@ -160,11 +196,12 @@ export default class CodeGraph extends BasePage {
160196
}
161197

162198
async isNodeVisibleInLastChatPath(node: string): Promise<boolean> {
199+
await this.locateNodeInLastChatPath(node).waitFor({ state: 'visible' });
163200
return await this.locateNodeInLastChatPath(node).isVisible();
164201
}
165202

166-
async isNotificationNoPathFound(): Promise<boolean> {
167-
return await this.notificationNoPathFound.isVisible();
203+
async isNotificationError(): Promise<boolean> {
204+
return await this.notificationError.isVisible();
168205
}
169206

170207
/* CodeGraph functionality */
@@ -173,4 +210,49 @@ export default class CodeGraph extends BasePage {
173210
await this.selectGraphInComboBox(graph).waitFor({ state : 'visible'})
174211
await this.selectGraphInComboBox(graph).click();
175212
}
213+
214+
async createProject(url : string): Promise<void> {
215+
await this.clickCreateNewProjectBtn();
216+
await this.typeUrlInput.fill(url);
217+
await this.createBtnInCreateProjectDialog.click();
218+
await this.createProjectWaitDialog.waitFor({ state : 'hidden'});
219+
}
220+
221+
async isGraphCreated(graph: string): Promise<boolean> {
222+
await this.comboBoxbtn.click();
223+
return await this.dialogCreatedGraphsList(graph).isVisible();
224+
}
225+
226+
async fillSearchBar(searchValue: string): Promise<void> {
227+
await this.searchBarInput.fill(searchValue);
228+
}
229+
230+
async getSearchAutoCompleteCount(): Promise<number> {
231+
return await this.searchBarAutoCompleteOptions.count();
232+
}
233+
234+
async getSearchBarElementsText(): Promise<string[]> {
235+
return await this.searchBarElements.allTextContents();
236+
}
237+
238+
async selectSearchBarOptionBtn(buttonNum: string): Promise<void> {
239+
await this.searchBarOptionBtn(buttonNum).click();
240+
}
241+
242+
async getSearchBarInputValue(): Promise<string> {
243+
return await this.searchBarInput.inputValue();
244+
}
245+
246+
247+
async scrollToBottomInSearchBarList(): Promise<void> {
248+
await this.searchBarList.evaluate((element) => {
249+
element.scrollTop = element.scrollHeight;
250+
})};
251+
252+
async isScrolledToBottomInSearchBarList(): Promise<boolean> {
253+
return await this.searchBarList.evaluate((element) => {
254+
return element.scrollTop + element.clientHeight >= element.scrollHeight;
255+
});
256+
}
257+
176258
}

e2e/logic/api/apiCalls.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { getRequest, postRequest } from "../../infra/api/apiRequests";
22
import urls from '../../config/urls.json'
3-
import { askQuestionResponse, createProjectResponse, fetchLatestRepoInfo, getProjectResponse, showPathResponse } from "./apiResponse";
3+
import { askQuestionResponse, createProjectResponse, fetchLatestRepoInfo, getNodeNeighborsResponse, getProjectResponse, searchAutoCompleteResponse, showPathResponse } from "./apiResponse";
44

55
export class ApiCalls {
66

7-
async createProject(projectname: string): Promise<createProjectResponse>{
8-
const result = await postRequest(urls.baseUrl + "api/repo?url=" + projectname);
7+
async createProject(projectUrl: string): Promise<createProjectResponse>{
8+
const result = await postRequest(urls.baseUrl + "api/repo?url=" + projectUrl);
99
return await result.json();
1010
}
1111

@@ -29,4 +29,14 @@ export class ApiCalls {
2929
return await result.json()
3030
}
3131

32+
async searchAutoComplete(projectName: string, searchInput: string): Promise<searchAutoCompleteResponse>{
33+
const result = await postRequest(urls.baseUrl + "api/repo/" + projectName + "?prefix=" + searchInput + "&type=autoComplete");
34+
return await result.json()
35+
}
36+
37+
async getNodeNeighbors(projectName: string, nodeNumber: string): Promise<getNodeNeighborsResponse>{
38+
const result = await getRequest(urls.baseUrl + "api/repo/" + projectName + "/" + nodeNumber);
39+
return await result.json()
40+
}
41+
3242
}

e2e/logic/api/apiResponse.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,54 @@ export interface askQuestionResponse{
7373
}
7474
}
7575

76+
export interface searchAutoCompleteResponse {
77+
result: {
78+
completions: {
79+
alias: string;
80+
id: number;
81+
labels: string[];
82+
properties: {
83+
args: any[];
84+
name: string;
85+
path: string;
86+
src_end: number;
87+
src_start: number;
88+
};
89+
}[];
90+
status: string;
91+
};
92+
}
93+
94+
export interface getNodeNeighborsResponse {
95+
result: {
96+
neighbors: {
97+
edges: {
98+
alias: string;
99+
dest_node: number;
100+
id: number;
101+
properties: Record<string, any>;
102+
relation: string;
103+
src_node: number;
104+
}[];
105+
nodes: {
106+
alias: string;
107+
id: number;
108+
labels: string[];
109+
properties: {
110+
args: [string, string][];
111+
doc?: string;
112+
name: string;
113+
path: string;
114+
ret_type?: string;
115+
src: string;
116+
src_end: number;
117+
src_start: number;
118+
};
119+
}[];
120+
};
121+
status: string;
122+
};
123+
}
124+
125+
126+

e2e/logic/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,18 @@
1+
import { Locator } from "@playwright/test";
12

23
export const delay = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
4+
5+
export const waitToBeEnabled = async (locator: Locator, timeout: number = 5000): Promise<boolean> => {
6+
const startTime = Date.now();
7+
8+
while (Date.now() - startTime < timeout) {
9+
if (await locator.isEnabled()) {
10+
return true;
11+
}
12+
await new Promise(resolve => setTimeout(resolve, 100));
13+
}
14+
15+
return false;
16+
};
17+
18+

e2e/tests/chat.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ test.describe("Chat tests", () => {
3030
await chat.selectGraph(GRAPH_ID);
3131
const isLoadingArray: boolean[] = [];
3232

33-
for (let i = 0; i < 10; i++) {
33+
for (let i = 0; i < 5; i++) {
3434
await chat.sendMessage(Node_Question);
3535
const isLoading: boolean = await chat.getpreviousQuestionLoadingImage();
3636
isLoadingArray.push(isLoading);
@@ -44,7 +44,7 @@ test.describe("Chat tests", () => {
4444
test("Verify auto-scroll and manual scroll in chat", async () => {
4545
const chat = await browser.createNewPage(CodeGraph, urls.baseUrl);
4646
await chat.selectGraph(GRAPH_ID);
47-
for (let i = 0; i < 10; i++) {
47+
for (let i = 0; i < 5; i++) {
4848
await chat.sendMessage(Node_Question);
4949
}
5050
await delay(500);
@@ -62,7 +62,6 @@ test.describe("Chat tests", () => {
6262
await chat.clickOnshowPathBtn();
6363
await chat.insertInputForShowPath("1", Node_Import_Data);
6464
await chat.insertInputForShowPath("2", Node_Add_Edge);
65-
await delay(500);
6665
expect(await chat.isNodeVisibleInLastChatPath(Node_Import_Data)).toBe(true);
6766
expect(await chat.isNodeVisibleInLastChatPath(Node_Add_Edge)).toBe(true);
6867
});
@@ -74,14 +73,15 @@ test.describe("Chat tests", () => {
7473
await chat.insertInputForShowPath("1", Node_Add_Edge);
7574
await chat.insertInputForShowPath("2", Node_Import_Data);
7675
await delay(500);
77-
expect(await chat.isNotificationNoPathFound()).toBe(true);
76+
expect(await chat.isNotificationError()).toBe(true);
7877
});
7978

8079
test("Validate error notification when sending an empty question in chat", async () => {
8180
const chat = await browser.createNewPage(CodeGraph, urls.baseUrl);
8281
await chat.selectGraph(GRAPH_ID);
8382
await chat.clickAskquestionBtn();
84-
expect(await chat.isNotificationNoPathFound()).toBe(true);
83+
await delay(500);
84+
expect(await chat.isNotificationError()).toBe(true);
8585
});
8686

8787
});

e2e/tests/codeGraph.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { test, expect } from "@playwright/test";
2+
import BrowserWrapper from "../infra/ui/browserWrapper";
3+
import CodeGraph from "../logic/POM/codeGraph";
4+
import urls from "../config/urls.json";
5+
import { GRAPH_ID } from "../config/constants";
6+
import { delay } from "../logic/utils";
7+
import { searchData, specialCharacters } from "../config/testData";
8+
9+
test.describe("Code graph tests", () => {
10+
let browser: BrowserWrapper;
11+
12+
test.beforeAll(async () => {
13+
browser = new BrowserWrapper();
14+
});
15+
16+
test.afterAll(async () => {
17+
await browser.closeBrowser();
18+
});
19+
20+
searchData.slice(0, 2).forEach(({ searchInput }) => {
21+
test(`Verify search bar auto-complete behavior for input: ${searchInput} via UI`, async () => {
22+
const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl);
23+
await codeGraph.selectGraph(GRAPH_ID);
24+
await codeGraph.fillSearchBar(searchInput);
25+
await delay(1000);
26+
const textList = await codeGraph.getSearchBarElementsText();
27+
textList.forEach((text) => {
28+
expect(text.toLowerCase()).toContain(searchInput);
29+
});
30+
});
31+
});
32+
33+
searchData.slice(2, 4).forEach(({ searchInput, completedSearchInput }) => {
34+
test(`Validate search bar updates with selected element: ${searchInput}`, async () => {
35+
const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl);
36+
await codeGraph.selectGraph(GRAPH_ID);
37+
await codeGraph.fillSearchBar(searchInput);
38+
await delay(1000);
39+
await codeGraph.selectSearchBarOptionBtn("1");
40+
expect(await codeGraph.getSearchBarInputValue()).toBe(
41+
completedSearchInput
42+
);
43+
});
44+
});
45+
46+
searchData.slice(0, 2).forEach(({ searchInput }) => {
47+
test(`Verify auto-scroll scroll in search bar list for: ${searchInput}`, async () => {
48+
const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl);
49+
await codeGraph.selectGraph(GRAPH_ID);
50+
await codeGraph.fillSearchBar(searchInput);
51+
await codeGraph.scrollToBottomInSearchBarList();
52+
expect(await codeGraph.isScrolledToBottomInSearchBarList()).toBe(true);
53+
});
54+
});
55+
56+
specialCharacters.forEach(({ character, expectedRes }) => {
57+
test(`Verify entering special characters behavior in search bar for: ${character}`, async () => {
58+
const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl);
59+
await codeGraph.selectGraph(GRAPH_ID);
60+
await codeGraph.fillSearchBar(character);
61+
await delay(1000);
62+
expect(await codeGraph.isNotificationError()).toBe(expectedRes);
63+
});
64+
});
65+
});

0 commit comments

Comments
 (0)