From f4a44c8973b6c437080ebe1c4eb04b837f74ea3c Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 25 Nov 2024 22:47:18 -0600 Subject: [PATCH 01/17] feat: baseline tools, not working on waiting for profile to load --- .gitignore | 2 + .../frontend/src/app/profile/page.tsx | 14 ++- .../frontend/src/tests/auth.spec.ts | 29 +++-- .../frontend/src/tests/fixtures/index.ts | 108 ++++++++++++++++-- .../frontend/src/tests/pages/base.page.ts | 16 +++ .../frontend/src/tests/pages/navbar.page.ts | 49 ++++++++ .../frontend/src/tests/pages/profile.page.ts | 37 ++++++ .../frontend/src/tests/profile.spec.ts | 49 ++++++++ 8 files changed, 275 insertions(+), 29 deletions(-) create mode 100644 autogpt_platform/frontend/src/tests/pages/base.page.ts create mode 100644 autogpt_platform/frontend/src/tests/pages/navbar.page.ts create mode 100644 autogpt_platform/frontend/src/tests/pages/profile.page.ts create mode 100644 autogpt_platform/frontend/src/tests/profile.spec.ts diff --git a/.gitignore b/.gitignore index 6590e212993c..225a4b93b029 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,5 @@ ig* .github_access_token LICENSE.rtf autogpt_platform/backend/settings.py +/.auth +/autogpt_platform/frontend/.auth diff --git a/autogpt_platform/frontend/src/app/profile/page.tsx b/autogpt_platform/frontend/src/app/profile/page.tsx index 93d9b77085cf..89a3c3074006 100644 --- a/autogpt_platform/frontend/src/app/profile/page.tsx +++ b/autogpt_platform/frontend/src/app/profile/page.tsx @@ -41,11 +41,11 @@ export default function PrivatePage() { const [confirmationDialogState, setConfirmationDialogState] = useState< | { - open: true; - message: string; - onConfirm: () => void; - onReject: () => void; - } + open: true; + message: string; + onConfirm: () => void; + onReject: () => void; + } | { open: false } >({ open: false }); @@ -143,7 +143,9 @@ export default function PrivatePage() { return (
-

Hello {user.email}

+

+ Hello {user.email} +

@@ -139,6 +141,7 @@ export const BlocksControl: React.FC = ({ htmlFor="search-blocks" className="whitespace-nowrap text-base font-bold text-black 2xl:text-xl" data-id="blocks-control-label" + data-testid="blocks-control-blocks-label" > Blocks @@ -201,10 +204,14 @@ export const BlocksControl: React.FC = ({ {beautifyString(block.name).replace(/ Block$/, "")} - + {/* Cap description at 100 characters max */} {block.description?.length > 100 ? block.description.slice(0, 100) + "..." @@ -214,6 +221,7 @@ export const BlocksControl: React.FC = ({
diff --git a/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx b/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx index f3db18845a4b..ec06279efb35 100644 --- a/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx +++ b/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx @@ -59,6 +59,7 @@ export const ControlPanel = ({ size="icon" onClick={() => control.onClick()} data-id={`control-button-${index}`} + data-testid={`blocks-control-${control.label.toLowerCase()}-button`} disabled={control.disabled || false} > {control.icon} diff --git a/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx b/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx index 9e558a54ac36..2679873cbb1f 100644 --- a/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx +++ b/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx @@ -91,6 +91,8 @@ export const SaveControl = ({ variant="ghost" size="icon" data-id="save-control-popover-trigger" + data-testid="blocks-control-save-button" + name="Save" > @@ -115,6 +117,7 @@ export const SaveControl = ({ value={agentName} onChange={(e) => onNameChange(e.target.value)} data-id="save-control-name-input" + data-testid="save-control-name-input" /> onDescriptionChange(e.target.value)} data-id="save-control-description-input" + data-testid="save-control-description-input" /> {agentMeta?.version && ( <> @@ -134,6 +138,7 @@ export const SaveControl = ({ className="col-span-3" value={agentMeta?.version || "-"} disabled + data-testid="save-control-version-output" /> )} @@ -144,6 +149,7 @@ export const SaveControl = ({ className="w-full" onClick={handleSave} data-id="save-control-save-agent" + data-testid="save-control-save-agent-button" > Save {getType()} From 9fbf52cd032c85085a3b1381a2c6e2335d0db043 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 27 Nov 2024 01:17:16 -0600 Subject: [PATCH 08/17] feat(frontend): first pass at tests --- .../frontend/src/tests/build.spec.ts | 65 ++++++++ .../frontend/src/tests/pages/build.page.ts | 142 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 autogpt_platform/frontend/src/tests/build.spec.ts create mode 100644 autogpt_platform/frontend/src/tests/pages/build.page.ts diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts new file mode 100644 index 000000000000..36b1e17772b9 --- /dev/null +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -0,0 +1,65 @@ +// profile.spec.ts +import { test } from "./fixtures"; +import { BuildPage } from "./pages/build.page"; + +test.describe("Build", () => { + let buildPage: BuildPage; + + test.beforeEach(async ({ page, loginPage, testUser }, testInfo) => { + buildPage = new BuildPage(page); + + // Start each test with login using worker auth + await page.goto("/login"); + await loginPage.login(testUser.email, testUser.password); + await test.expect(page).toHaveURL("/"); + await buildPage.navbar.clickBuildLink(); + }); + + test("user can add a block", async ({ page }) => { + await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); + await test.expect(page).toHaveURL(new RegExp("/.*build")); + await buildPage.closeTutorial(); + await buildPage.openBlocksPanel(); + const block = { + id: "31d1064e-7446-4693-a7d4-65e5ca1180d1", + name: "Add to Dictionary", + description: "Add to Dictionary", + }; + await buildPage.addBlock(block); + await buildPage.closeBlocksPanel(); + await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy(); + }); + + test("user can add all blocks", async ({ page }, testInfo) => { + // this test is slow af so we 10x the timeout (sorry future me) + await test.setTimeout(testInfo.timeout * 10); + await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); + await test.expect(page).toHaveURL(new RegExp("/.*build")); + await buildPage.closeTutorial(); + await buildPage.openBlocksPanel(); + const blocks = await buildPage.getBlocks(); + + // add all the blocks in order + for (const block of blocks) { + await buildPage.addBlock(block); + } + await buildPage.closeBlocksPanel(); + // check that all the blocks are visible + for (const block of blocks) { + await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy(); + } + // check that we can save the agent with all the blocks + await buildPage.saveAgent("all blocks test", "all blocks test"); + // page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340 + await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); + }); + + test("build navigation is accessible from navbar", async ({ page }) => { + await buildPage.navbar.clickBuildLink(); + await test.expect(page).toHaveURL(new RegExp("/build")); + // workaround for #8788 + await page.reload(); + await page.reload(); + await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); + }); +}); diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts new file mode 100644 index 000000000000..417077ad2c07 --- /dev/null +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -0,0 +1,142 @@ +import { ElementHandle, Page } from "@playwright/test"; +import { BasePage } from "./base.page"; + +interface Block { + id: string; + name: string; + description: string; +} + +export class BuildPage extends BasePage { + constructor(page: Page) { + super(page); + } + + async closeTutorial(): Promise { + await this.page.getByRole("button", { name: "Skip Tutorial" }).click(); + } + + async openBlocksPanel(): Promise { + if ( + !(await this.page.getByTestId("blocks-control-blocks-label").isVisible()) + ) { + await this.page.getByTestId("blocks-control-blocks-button").click(); + } + } + + async closeBlocksPanel(): Promise { + if ( + await this.page.getByTestId("blocks-control-blocks-label").isVisible() + ) { + await this.page.getByTestId("blocks-control-blocks-button").click(); + } + } + + async saveAgent( + name: string = "Test Agent", + description: string = "", + ): Promise { + await this.page.getByTestId("blocks-control-save-button").click(); + await this.page.getByTestId("save-control-name-input").fill(name); + await this.page + .getByTestId("save-control-description-input") + .fill(description); + await this.page.getByTestId("save-control-save-agent-button").click(); + } + + async getBlocks(): Promise { + try { + const blocks = await this.page.locator('[data-id^="block-card-"]').all(); + + console.log(`found ${blocks.length} blocks`); + + const results = await Promise.all( + blocks.map(async (block) => { + try { + const fullId = (await block.getAttribute("data-id")) || ""; + const id = fullId.replace("block-card-", ""); + const nameElement = block.locator('[data-testid^="block-name-"]'); + const descriptionElement = block.locator( + '[data-testid^="block-description-"]', + ); + + const name = (await nameElement.textContent()) || ""; + const description = (await descriptionElement.textContent()) || ""; + + return { + id, + name: name.trim(), + description: description.trim(), + }; + } catch (elementError) { + console.error("Error processing block:", elementError); + return null; + } + }), + ); + + // Filter out any null results from errors + return results.filter((block): block is Block => block !== null); + } catch (error) { + console.error("Error getting blocks:", error); + return []; + } + } + + async addBlock(block: Block): Promise { + console.log(`adding block ${block.id} ${block.name} to agent`); + await this.page.getByTestId(`block-name-${block.id}`).click(); + } + + async isRFNodeVisible(nodeId: string): Promise { + return await this.page.getByTestId(`rf__node-${nodeId}`).isVisible(); + } + + // async hasBlock(block: Block): Promise { + // // all blocks on graph have a ref id getByTestId('rf__node-1') where -1 is the order it was added to the graph or the internal id after its been saved + // //so we can check by getting all elements with that testid and seeing if the textContent includes the block name + // const nodes = await this.page.locator('[data-testid^="rf__node-"]').all(); + // console.log(`found ${nodes.length} nodes`); + // const nodeTexts = await Promise.all( + // nodes.map((node) => node.textContent()), + // ); + // console.log(`nodeTexts: ${nodeTexts}`); + // const matches = nodeTexts.some((text) => text?.includes(block.name)); + // console.log(`matches: ${matches}`); + // return matches; + // } + async hasBlock(block: Block): Promise { + try { + // Use both ID and name for most precise matching + const node = await this.page + .locator(`[data-blockid="${block.id}"]`) + .first(); + return await node.isVisible(); + } catch (error) { + console.error("Error checking for block:", error); + return false; + } + } + + async getBlockInputs(blockId: string): Promise { + try { + const node = await this.page + .locator(`[data-blockid="${blockId}"]`) + .first(); + const inputsData = await node.getAttribute("data-inputs"); + return inputsData ? JSON.parse(inputsData) : []; + } catch (error) { + console.error("Error getting block inputs:", error); + return []; + } + } + + async isLoaded(): Promise { + try { + await this.page.waitForLoadState("networkidle", { timeout: 10_000 }); + return true; + } catch (error) { + return false; + } + } +} From a84bf6dc61858710c1377859ec7443aa29383559 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 27 Nov 2024 02:33:49 -0600 Subject: [PATCH 09/17] ref: don't save it without filling the required fields --- autogpt_platform/frontend/src/tests/build.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index 36b1e17772b9..db72973c8857 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -51,7 +51,7 @@ test.describe("Build", () => { // check that we can save the agent with all the blocks await buildPage.saveAgent("all blocks test", "all blocks test"); // page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340 - await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); + // await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); }); test("build navigation is accessible from navbar", async ({ page }) => { From 39646cda008ff95382b4326a0139295048c845e5 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 27 Nov 2024 03:18:03 -0600 Subject: [PATCH 10/17] fix: agent not saving in test --- .../frontend/src/tests/build.spec.ts | 13 +++++- .../frontend/src/tests/pages/build.page.ts | 44 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index db72973c8857..4c2a31c76d69 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -48,10 +48,21 @@ test.describe("Build", () => { for (const block of blocks) { await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy(); } + // fill in the input for the agent input block + await buildPage.fillBlockInputByPlaceholder( + blocks.find((b) => b.name === "Agent Input")?.id ?? "", + "Enter Name", + "Agent Input Field", + ); + await buildPage.fillBlockInputByPlaceholder( + blocks.find((b) => b.name === "Agent Output")?.id ?? "", + "Enter Name", + "Agent Output Field", + ); // check that we can save the agent with all the blocks await buildPage.saveAgent("all blocks test", "all blocks test"); // page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340 - // await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); + await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); }); test("build navigation is accessible from navbar", async ({ page }) => { diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index 417077ad2c07..50394609fbcd 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -131,6 +131,50 @@ export class BuildPage extends BasePage { } } + async getBlockOutputs(blockId: string): Promise { + throw new Error("Not implemented"); + // try { + // const node = await this.page + // .locator(`[data-blockid="${blockId}"]`) + // .first(); + // const outputsData = await node.getAttribute("data-outputs"); + // return outputsData ? JSON.parse(outputsData) : []; + // } catch (error) { + // console.error("Error getting block outputs:", error); + // return []; + // } + } + + async fillBlockInputByPlaceholder( + blockId: string, + placeholder: string, + value: string, + ): Promise { + const block = await this.page.locator(`[data-blockid="${blockId}"]`); + const input = await block.getByPlaceholder(placeholder); + await input.fill(value); + } + + async fillBlockInputByLabel( + blockId: string, + label: string, + value: string, + ): Promise { + throw new Error("Not implemented"); + // const block = await this.page.locator(`[data-blockid="${blockId}"]`); + // const input = await block.getByLabel(label); + // await input.fill(value); + } + + async connectBlockOutputToBlockInput( + blockOutputId: string, + blockOutputName: string, + blockInputId: string, + blockInputName: string, + ): Promise { + throw new Error("Not implemented"); + } + async isLoaded(): Promise { try { await this.page.waitForLoadState("networkidle", { timeout: 10_000 }); From aff25808e03960c1b2f139a05c04a3fdf7e53131 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 27 Nov 2024 04:32:22 -0600 Subject: [PATCH 11/17] fix: linting --- .../frontend/src/components/edit/control/BlocksControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx b/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx index 0515b6970dde..95014eb49e8f 100644 --- a/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx +++ b/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx @@ -215,7 +215,7 @@ export const BlocksControl: React.FC = ({ truncateLengthLimit={45} />
- From 745f75a77a144a11c8082f25f59681bc06a1675d Mon Sep 17 00:00:00 2001 From: Bently Date: Mon, 2 Dec 2024 20:33:51 +0000 Subject: [PATCH 12/17] feat(frontend): add block connecting test (#8862) --- .../frontend/src/tests/build.spec.ts | 62 +++++++++++++++++++ .../frontend/src/tests/pages/build.page.ts | 20 +++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index 4c2a31c76d69..1705bb13704f 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -73,4 +73,66 @@ test.describe("Build", () => { await page.reload(); await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); }); + + test("user can add two blocks and connect them", async ({ page }, testInfo) => { + await test.setTimeout(testInfo.timeout * 10); + + await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); + await test.expect(page).toHaveURL(new RegExp("/.*build")); + await buildPage.closeTutorial(); + await buildPage.openBlocksPanel(); + + // Define the blocks to add + const block1 = { + id: "1ff065e9-88e8-4358-9d82-8dc91f622ba9", + name: "Store Value 1", + description: "Store Value Block 1", + }; + const block2 = { + id: "1ff065e9-88e8-4358-9d82-8dc91f622ba9", + name: "Store Value 2", + description: "Store Value Block 2", + }; + + // Add the blocks + await buildPage.addBlock(block1); + await buildPage.addBlock(block2); + await buildPage.closeBlocksPanel(); + + // Connect the blocks + await buildPage.connectBlockOutputToBlockInput( + "1-1-output-source", + "Output Source", + "1-2-input-target", + "Input Target" + ); + + // Fill in the input for the first block + await buildPage.fillBlockInputByPlaceholder( + block1.id, + "Enter input", + "Test Value" + ); + + // Save the agent and wait for the URL to update + await buildPage.saveAgent("Connected Blocks Test", "Testing block connections"); + await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); + + // Wait for the save button to be enabled again + await page.waitForSelector('[data-testid="blocks-control-save-button"]:not([disabled])'); + + // Ensure the run button is enabled + const runButton = page.locator('[data-id="primary-action-run-agent"]'); + await test.expect(runButton).toBeEnabled(); + + // Run the agent + await runButton.click(); + + // Wait for processing to complete by checking the completion badge + await page.waitForSelector('[data-id^="badge-"][data-id$="-COMPLETED"]'); + + // Get the first completion badge and verify it's visible + const completionBadge = page.locator('[data-id^="badge-"][data-id$="-COMPLETED"]').first(); + await test.expect(completionBadge).toBeVisible(); + }); }); diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index 50394609fbcd..dd6bee771180 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -172,7 +172,25 @@ export class BuildPage extends BasePage { blockInputId: string, blockInputName: string, ): Promise { - throw new Error("Not implemented"); + try { + // Locate the output element + const outputElement = await this.page.locator(`[data-id="${blockOutputId}"]`); + // Locate the input element + const inputElement = await this.page.locator(`[data-id="${blockInputId}"]`); + + // Drag from the output to the input + const outputBox = await outputElement.boundingBox(); + const inputBox = await inputElement.boundingBox(); + + if (outputBox && inputBox) { + await this.page.mouse.move(outputBox.x + outputBox.width / 2, outputBox.y + outputBox.height / 2); + await this.page.mouse.down(); + await this.page.mouse.move(inputBox.x + inputBox.width / 2, inputBox.y + inputBox.height / 2); + await this.page.mouse.up(); + } + } catch (error) { + console.error("Error connecting block output to input:", error); + } } async isLoaded(): Promise { From 0a787ba1a5fa8f665ee2f3b8e97ed9321857f8cc Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 2 Dec 2024 16:10:49 -0600 Subject: [PATCH 13/17] fix: formatting --- .../frontend/src/tests/build.spec.ts | 21 +++++++++++++------ .../frontend/src/tests/pages/build.page.ts | 18 ++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index 1705bb13704f..51e26be7a22b 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -74,7 +74,9 @@ test.describe("Build", () => { await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); }); - test("user can add two blocks and connect them", async ({ page }, testInfo) => { + test("user can add two blocks and connect them", async ({ + page, + }, testInfo) => { await test.setTimeout(testInfo.timeout * 10); await test.expect(buildPage.isLoaded()).resolves.toBeTruthy(); @@ -104,22 +106,27 @@ test.describe("Build", () => { "1-1-output-source", "Output Source", "1-2-input-target", - "Input Target" + "Input Target", ); // Fill in the input for the first block await buildPage.fillBlockInputByPlaceholder( block1.id, "Enter input", - "Test Value" + "Test Value", ); // Save the agent and wait for the URL to update - await buildPage.saveAgent("Connected Blocks Test", "Testing block connections"); + await buildPage.saveAgent( + "Connected Blocks Test", + "Testing block connections", + ); await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); // Wait for the save button to be enabled again - await page.waitForSelector('[data-testid="blocks-control-save-button"]:not([disabled])'); + await page.waitForSelector( + '[data-testid="blocks-control-save-button"]:not([disabled])', + ); // Ensure the run button is enabled const runButton = page.locator('[data-id="primary-action-run-agent"]'); @@ -132,7 +139,9 @@ test.describe("Build", () => { await page.waitForSelector('[data-id^="badge-"][data-id$="-COMPLETED"]'); // Get the first completion badge and verify it's visible - const completionBadge = page.locator('[data-id^="badge-"][data-id$="-COMPLETED"]').first(); + const completionBadge = page + .locator('[data-id^="badge-"][data-id$="-COMPLETED"]') + .first(); await test.expect(completionBadge).toBeVisible(); }); }); diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index dd6bee771180..e65df96e207c 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -174,18 +174,28 @@ export class BuildPage extends BasePage { ): Promise { try { // Locate the output element - const outputElement = await this.page.locator(`[data-id="${blockOutputId}"]`); + const outputElement = await this.page.locator( + `[data-id="${blockOutputId}"]`, + ); // Locate the input element - const inputElement = await this.page.locator(`[data-id="${blockInputId}"]`); + const inputElement = await this.page.locator( + `[data-id="${blockInputId}"]`, + ); // Drag from the output to the input const outputBox = await outputElement.boundingBox(); const inputBox = await inputElement.boundingBox(); if (outputBox && inputBox) { - await this.page.mouse.move(outputBox.x + outputBox.width / 2, outputBox.y + outputBox.height / 2); + await this.page.mouse.move( + outputBox.x + outputBox.width / 2, + outputBox.y + outputBox.height / 2, + ); await this.page.mouse.down(); - await this.page.mouse.move(inputBox.x + inputBox.width / 2, inputBox.y + inputBox.height / 2); + await this.page.mouse.move( + inputBox.x + inputBox.width / 2, + inputBox.y + inputBox.height / 2, + ); await this.page.mouse.up(); } } catch (error) { From 383215cab30beac3410da4cc6e693336629dac4f Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 2 Dec 2024 16:20:56 -0600 Subject: [PATCH 14/17] fix: data ids --- autogpt_platform/frontend/src/tests/build.spec.ts | 1 + .../frontend/src/tests/pages/build.page.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index 51e26be7a22b..b4fb3a0c08f9 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -114,6 +114,7 @@ test.describe("Build", () => { block1.id, "Enter input", "Test Value", + "1", ); // Save the agent and wait for the URL to update diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index e65df96e207c..b04f67ec610a 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -145,12 +145,21 @@ export class BuildPage extends BasePage { // } } + // dataId is optional, if provided, it will start the search with that container, otherwise it will start with the blockId + // this is useful if you have multiple blocks with the same id, but different dataIds which you should have when adding a block to the graph. + // Do note that once you run an agent, the dataId will change, so you will need to update the tests to use the new dataId or not use the same block in tests that run an agent async fillBlockInputByPlaceholder( blockId: string, placeholder: string, value: string, + dataId?: string, ): Promise { - const block = await this.page.locator(`[data-blockid="${blockId}"]`); + // If dataId is provided, start with that container, otherwise use blockId + let selector = dataId + ? `[data-id="${dataId}"] [data-blockid="${blockId}"]` + : `[data-blockid="${blockId}"]`; + + const block = await this.page.locator(selector); const input = await block.getByPlaceholder(placeholder); await input.fill(value); } From a30052e3fd88c88a8cae0b1c365fd65490534a26 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 2 Dec 2024 16:41:07 -0600 Subject: [PATCH 15/17] ref: extract the specific calls to functions --- .../frontend/src/tests/build.spec.ts | 18 +++++------- .../frontend/src/tests/pages/build.page.ts | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index b4fb3a0c08f9..c42b4c0effd4 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -125,24 +125,20 @@ test.describe("Build", () => { await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+")); // Wait for the save button to be enabled again - await page.waitForSelector( - '[data-testid="blocks-control-save-button"]:not([disabled])', - ); + await buildPage.waitForSaveButton(); // Ensure the run button is enabled - const runButton = page.locator('[data-id="primary-action-run-agent"]'); - await test.expect(runButton).toBeEnabled(); + await test.expect(buildPage.isRunButtonEnabled()).resolves.toBeTruthy(); // Run the agent - await runButton.click(); + await buildPage.runAgent(); // Wait for processing to complete by checking the completion badge - await page.waitForSelector('[data-id^="badge-"][data-id$="-COMPLETED"]'); + await buildPage.waitForCompletionBadge(); // Get the first completion badge and verify it's visible - const completionBadge = page - .locator('[data-id^="badge-"][data-id$="-COMPLETED"]') - .first(); - await test.expect(completionBadge).toBeVisible(); + await test + .expect(buildPage.isCompletionBadgeVisible()) + .resolves.toBeTruthy(); }); }); diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index b04f67ec610a..da84566bd77d 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -220,4 +220,33 @@ export class BuildPage extends BasePage { return false; } } + + async isRunButtonEnabled(): Promise { + const runButton = this.page.locator('[data-id="primary-action-run-agent"]'); + return await runButton.isEnabled(); + } + + async runAgent(): Promise { + const runButton = this.page.locator('[data-id="primary-action-run-agent"]'); + await runButton.click(); + } + + async waitForCompletionBadge(): Promise { + await this.page.waitForSelector( + '[data-id^="badge-"][data-id$="-COMPLETED"]', + ); + } + + async waitForSaveButton(): Promise { + await this.page.waitForSelector( + '[data-testid="blocks-control-save-button"]:not([disabled])', + ); + } + + async isCompletionBadgeVisible(): Promise { + const completionBadge = this.page + .locator('[data-id^="badge-"][data-id$="-COMPLETED"]') + .first(); + return await completionBadge.isVisible(); + } } From 1e4d7c966e67f20d5ed3dd4b3acf3441f9dde693 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 2 Dec 2024 18:12:31 -0600 Subject: [PATCH 16/17] feat: build full agents and run them via the builder --- .../frontend/src/components/NodeHandle.tsx | 2 + .../components/runner-ui/RunnerInputBlock.tsx | 7 +- .../components/runner-ui/RunnerInputUI.tsx | 1 + .../frontend/src/tests/build.spec.ts | 84 +++++++++++- .../frontend/src/tests/pages/build.page.ts | 120 +++++++++++++----- 5 files changed, 180 insertions(+), 34 deletions(-) diff --git a/autogpt_platform/frontend/src/components/NodeHandle.tsx b/autogpt_platform/frontend/src/components/NodeHandle.tsx index b489cccd5569..21cfccb1e1a6 100644 --- a/autogpt_platform/frontend/src/components/NodeHandle.tsx +++ b/autogpt_platform/frontend/src/components/NodeHandle.tsx @@ -59,6 +59,7 @@ const NodeHandle: FC = ({
= ({
{placeholder_values.map((placeholder, index) => ( - + {placeholder.toString()} ))} @@ -49,6 +53,7 @@ export function InputBlock({ ) : ( onInputChange(id, "value", e.target.value)} placeholder={placeholder_values?.[0]?.toString() || "Enter value"} diff --git a/autogpt_platform/frontend/src/components/runner-ui/RunnerInputUI.tsx b/autogpt_platform/frontend/src/components/runner-ui/RunnerInputUI.tsx index 93e7fd7444b4..9dc6498491b3 100644 --- a/autogpt_platform/frontend/src/components/runner-ui/RunnerInputUI.tsx +++ b/autogpt_platform/frontend/src/components/runner-ui/RunnerInputUI.tsx @@ -72,6 +72,7 @@ export function RunnerInputUI({