Spaces:
Running
Running
Work around ReactFlow issue. Try making tests more robust.
Browse files
.gitignore
CHANGED
|
@@ -16,3 +16,9 @@ joblib-cache
|
|
| 16 |
*.egg-info
|
| 17 |
|
| 18 |
lynxkite_crdt_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
*.egg-info
|
| 17 |
|
| 18 |
lynxkite_crdt_data
|
| 19 |
+
|
| 20 |
+
# Playwright
|
| 21 |
+
/test-results/
|
| 22 |
+
/playwright-report/
|
| 23 |
+
/blob-report/
|
| 24 |
+
/playwright/.cache/
|
lynxkite-app/web/src/workspace/Workspace.tsx
CHANGED
|
@@ -197,9 +197,10 @@ function LynxKiteFlow() {
|
|
| 197 |
});
|
| 198 |
}
|
| 199 |
};
|
| 200 |
-
|
|
|
|
| 201 |
return () => {
|
| 202 |
-
document.removeEventListener("
|
| 203 |
};
|
| 204 |
}, [catalog.data, nodeSearchSettings, state.workspace.env]);
|
| 205 |
|
|
|
|
| 197 |
});
|
| 198 |
}
|
| 199 |
};
|
| 200 |
+
// TODO: Switch to keydown once https://github.com/xyflow/xyflow/pull/5055 is merged.
|
| 201 |
+
document.addEventListener("keyup", handleKeyDown);
|
| 202 |
return () => {
|
| 203 |
+
document.removeEventListener("keyup", handleKeyDown);
|
| 204 |
};
|
| 205 |
}, [catalog.data, nodeSearchSettings, state.workspace.env]);
|
| 206 |
|
lynxkite-app/web/tests/basic.spec.ts
CHANGED
|
@@ -21,6 +21,9 @@ test("Box creation & deletion per env", async () => {
|
|
| 21 |
const envs = await workspace.getEnvs();
|
| 22 |
for (const env of envs) {
|
| 23 |
await workspace.setEnv(env);
|
|
|
|
|
|
|
|
|
|
| 24 |
const catalog = await workspace.getCatalog();
|
| 25 |
expect(catalog).not.toHaveLength(0);
|
| 26 |
const op = catalog[0];
|
|
|
|
| 21 |
const envs = await workspace.getEnvs();
|
| 22 |
for (const env of envs) {
|
| 23 |
await workspace.setEnv(env);
|
| 24 |
+
// TODO: Opening the catalog immediately after setting the env can fail.
|
| 25 |
+
// Let's fix this!
|
| 26 |
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
| 27 |
const catalog = await workspace.getCatalog();
|
| 28 |
expect(catalog).not.toHaveLength(0);
|
| 29 |
const op = catalog[0];
|
lynxkite-app/web/tests/examples.spec.ts
CHANGED
|
@@ -4,44 +4,44 @@ import { Workspace } from "./lynxkite";
|
|
| 4 |
|
| 5 |
test("LynxKite Graph Analytics example", async ({ page }) => {
|
| 6 |
const ws = await Workspace.open(page, "NetworkX demo");
|
| 7 |
-
|
| 8 |
});
|
| 9 |
|
| 10 |
test("Pytorch example", async ({ page }) => {
|
| 11 |
const ws = await Workspace.open(page, "PyTorch demo");
|
| 12 |
-
|
| 13 |
});
|
| 14 |
|
| 15 |
test.fail("AIMO example", async ({ page }) => {
|
| 16 |
// Fails because of missing OPENAI_API_KEY
|
| 17 |
const ws = await Workspace.open(page, "AIMO");
|
| 18 |
-
|
| 19 |
});
|
| 20 |
|
| 21 |
test.fail("LynxScribe example", async ({ page }) => {
|
| 22 |
// Fails because of missing OPENAI_API_KEY
|
| 23 |
const ws = await Workspace.open(page, "LynxScribe demo");
|
| 24 |
-
|
| 25 |
});
|
| 26 |
|
| 27 |
test.fail("Graph RAG", async ({ page }) => {
|
| 28 |
// Fails due to some issue with ChromaDB
|
| 29 |
const ws = await Workspace.open(page, "Graph RAG");
|
| 30 |
-
|
| 31 |
});
|
| 32 |
|
| 33 |
test.fail("RAG chatbot app", async ({ page }) => {
|
| 34 |
// Fail due to all operation being unknown
|
| 35 |
const ws = await Workspace.open(page, "RAG chatbot app");
|
| 36 |
-
|
| 37 |
});
|
| 38 |
|
| 39 |
test("Airlines demo", async ({ page }) => {
|
| 40 |
const ws = await Workspace.open(page, "Airlines demo");
|
| 41 |
-
|
| 42 |
});
|
| 43 |
|
| 44 |
test("Pillow example", async ({ page }) => {
|
| 45 |
const ws = await Workspace.open(page, "Image processing");
|
| 46 |
-
|
| 47 |
});
|
|
|
|
| 4 |
|
| 5 |
test("LynxKite Graph Analytics example", async ({ page }) => {
|
| 6 |
const ws = await Workspace.open(page, "NetworkX demo");
|
| 7 |
+
await ws.expectErrorFree(process.env.CI ? 2000 : 1000);
|
| 8 |
});
|
| 9 |
|
| 10 |
test("Pytorch example", async ({ page }) => {
|
| 11 |
const ws = await Workspace.open(page, "PyTorch demo");
|
| 12 |
+
await ws.expectErrorFree();
|
| 13 |
});
|
| 14 |
|
| 15 |
test.fail("AIMO example", async ({ page }) => {
|
| 16 |
// Fails because of missing OPENAI_API_KEY
|
| 17 |
const ws = await Workspace.open(page, "AIMO");
|
| 18 |
+
await ws.expectErrorFree();
|
| 19 |
});
|
| 20 |
|
| 21 |
test.fail("LynxScribe example", async ({ page }) => {
|
| 22 |
// Fails because of missing OPENAI_API_KEY
|
| 23 |
const ws = await Workspace.open(page, "LynxScribe demo");
|
| 24 |
+
await ws.expectErrorFree();
|
| 25 |
});
|
| 26 |
|
| 27 |
test.fail("Graph RAG", async ({ page }) => {
|
| 28 |
// Fails due to some issue with ChromaDB
|
| 29 |
const ws = await Workspace.open(page, "Graph RAG");
|
| 30 |
+
await ws.expectErrorFree(process.env.CI ? 2000 : 500);
|
| 31 |
});
|
| 32 |
|
| 33 |
test.fail("RAG chatbot app", async ({ page }) => {
|
| 34 |
// Fail due to all operation being unknown
|
| 35 |
const ws = await Workspace.open(page, "RAG chatbot app");
|
| 36 |
+
await ws.expectErrorFree();
|
| 37 |
});
|
| 38 |
|
| 39 |
test("Airlines demo", async ({ page }) => {
|
| 40 |
const ws = await Workspace.open(page, "Airlines demo");
|
| 41 |
+
await ws.expectErrorFree(process.env.CI ? 10000 : 500);
|
| 42 |
});
|
| 43 |
|
| 44 |
test("Pillow example", async ({ page }) => {
|
| 45 |
const ws = await Workspace.open(page, "Image processing");
|
| 46 |
+
await ws.expectErrorFree();
|
| 47 |
});
|
lynxkite-app/web/tests/lynxkite.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class Workspace {
|
|
| 54 |
|
| 55 |
async addBox(boxName) {
|
| 56 |
//TODO: Support passing box parameters (id, position, etc.)
|
| 57 |
-
const allBoxes = await this.getBoxes();
|
| 58 |
if (allBoxes) {
|
| 59 |
// Avoid overlapping with existing nodes
|
| 60 |
const numNodes = allBoxes.length || 1;
|
|
@@ -66,9 +66,7 @@ export class Workspace {
|
|
| 66 |
await this.page.locator(".ws-name").click();
|
| 67 |
await this.page.keyboard.press("/");
|
| 68 |
await this.page.locator(".node-search").getByText(boxName).click();
|
| 69 |
-
await this.
|
| 70 |
-
// Workaround to wait for the deselection animation after choosing a box. Otherwise, the next box will not be added.
|
| 71 |
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
| 72 |
}
|
| 73 |
|
| 74 |
async getCatalog() {
|
|
@@ -78,14 +76,22 @@ export class Workspace {
|
|
| 78 |
.allInnerTexts();
|
| 79 |
// Dismiss the catalog menu
|
| 80 |
await this.page.keyboard.press("Escape");
|
| 81 |
-
await
|
| 82 |
return catalog;
|
| 83 |
}
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
async deleteBoxes(boxIds: string[]) {
|
| 86 |
for (const boxId of boxIds) {
|
| 87 |
-
await this.
|
| 88 |
await this.page.keyboard.press("Backspace");
|
|
|
|
| 89 |
}
|
| 90 |
}
|
| 91 |
|
|
@@ -94,7 +100,7 @@ export class Workspace {
|
|
| 94 |
}
|
| 95 |
|
| 96 |
getBoxes() {
|
| 97 |
-
return this.page.locator(".react-flow__node")
|
| 98 |
}
|
| 99 |
|
| 100 |
getBoxHandle(boxId: string, pos?: string) {
|
|
@@ -143,19 +149,13 @@ export class Workspace {
|
|
| 143 |
await this.page.mouse.up();
|
| 144 |
}
|
| 145 |
|
| 146 |
-
async
|
| 147 |
// TODO: Workaround, to account for workspace execution. Once
|
| 148 |
// we have a load indicator we can use that instead.
|
| 149 |
await new Promise((resolve) =>
|
| 150 |
setTimeout(resolve, executionWaitTime ? executionWaitTime : 500),
|
| 151 |
);
|
| 152 |
-
|
| 153 |
-
for (const box of boxes) {
|
| 154 |
-
if (await box.locator(".error").isVisible()) {
|
| 155 |
-
return false;
|
| 156 |
-
}
|
| 157 |
-
}
|
| 158 |
-
return true;
|
| 159 |
}
|
| 160 |
|
| 161 |
async close() {
|
|
|
|
| 54 |
|
| 55 |
async addBox(boxName) {
|
| 56 |
//TODO: Support passing box parameters (id, position, etc.)
|
| 57 |
+
const allBoxes = await this.getBoxes().all();
|
| 58 |
if (allBoxes) {
|
| 59 |
// Avoid overlapping with existing nodes
|
| 60 |
const numNodes = allBoxes.length || 1;
|
|
|
|
| 66 |
await this.page.locator(".ws-name").click();
|
| 67 |
await this.page.keyboard.press("/");
|
| 68 |
await this.page.locator(".node-search").getByText(boxName).click();
|
| 69 |
+
await expect(this.getBoxes()).toHaveCount(allBoxes.length + 1);
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
async getCatalog() {
|
|
|
|
| 76 |
.allInnerTexts();
|
| 77 |
// Dismiss the catalog menu
|
| 78 |
await this.page.keyboard.press("Escape");
|
| 79 |
+
await expect(this.page.locator(".node-search")).not.toBeVisible();
|
| 80 |
return catalog;
|
| 81 |
}
|
| 82 |
|
| 83 |
+
async selectBox(boxId: string) {
|
| 84 |
+
const box = this.getBox(boxId);
|
| 85 |
+
// Click on the resizer, so we don't click on any parameters by accident.
|
| 86 |
+
await box.locator(".react-flow__resize-control").click();
|
| 87 |
+
await expect(box).toHaveClass(/selected/);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
async deleteBoxes(boxIds: string[]) {
|
| 91 |
for (const boxId of boxIds) {
|
| 92 |
+
await this.selectBox(boxId);
|
| 93 |
await this.page.keyboard.press("Backspace");
|
| 94 |
+
await expect(this.getBox(boxId)).not.toBeVisible();
|
| 95 |
}
|
| 96 |
}
|
| 97 |
|
|
|
|
| 100 |
}
|
| 101 |
|
| 102 |
getBoxes() {
|
| 103 |
+
return this.page.locator(".react-flow__node");
|
| 104 |
}
|
| 105 |
|
| 106 |
getBoxHandle(boxId: string, pos?: string) {
|
|
|
|
| 149 |
await this.page.mouse.up();
|
| 150 |
}
|
| 151 |
|
| 152 |
+
async expectErrorFree(executionWaitTime?) {
|
| 153 |
// TODO: Workaround, to account for workspace execution. Once
|
| 154 |
// we have a load indicator we can use that instead.
|
| 155 |
await new Promise((resolve) =>
|
| 156 |
setTimeout(resolve, executionWaitTime ? executionWaitTime : 500),
|
| 157 |
);
|
| 158 |
+
await expect(this.getBoxes().locator(".error")).not.toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
}
|
| 160 |
|
| 161 |
async close() {
|