mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(frontend): add block tests (#8804)
<!-- Clearly explain the need for these changes: --> We want to be able to automatically test agent running creation and building via the build page ### Changes 🏗️ - updates many UI elements to have new data ids - adds page for build - adds spec for build <!-- Concisely describe all of the changes made in this pull request: --> ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Run the UI Tests! --------- Co-authored-by: Bently <tomnoon9@gmail.com>
This commit is contained in:
@@ -566,6 +566,17 @@ export function CustomNode({
|
||||
className={`${blockClasses} ${errorClass} ${statusClass}`}
|
||||
data-id={`custom-node-${id}`}
|
||||
z-index={1}
|
||||
data-blockid={data.block_id}
|
||||
data-blockname={data.title}
|
||||
data-blocktype={data.blockType}
|
||||
data-nodetype={data.uiType}
|
||||
data-category={data.categories[0]?.category.toLowerCase() || ""}
|
||||
data-inputs={JSON.stringify(
|
||||
Object.keys(data.inputSchema?.properties || {}),
|
||||
)}
|
||||
data-outputs={JSON.stringify(
|
||||
Object.keys(data.outputSchema?.properties || {}),
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
|
||||
@@ -59,6 +59,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
<div key={keyName} className="handle-container">
|
||||
<Handle
|
||||
type="target"
|
||||
data-testid={`input-handle-${keyName}`}
|
||||
position={Position.Left}
|
||||
id={keyName}
|
||||
className="-ml-[26px]"
|
||||
@@ -76,6 +77,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
<div key={keyName} className="handle-container justify-end">
|
||||
<Handle
|
||||
type="source"
|
||||
data-testid={`output-handle-${keyName}`}
|
||||
position={Position.Right}
|
||||
id={keyName}
|
||||
className="group -mr-[26px]"
|
||||
|
||||
@@ -122,6 +122,8 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-id="blocks-control-popover-trigger"
|
||||
data-testid="blocks-control-blocks-button"
|
||||
name="Blocks"
|
||||
>
|
||||
<IconToyBrick />
|
||||
</Button>
|
||||
@@ -143,6 +145,7 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
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
|
||||
</Label>
|
||||
@@ -205,6 +208,7 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
<span
|
||||
className="block truncate pb-1 text-sm font-semibold"
|
||||
data-id={`block-name-${block.id}`}
|
||||
data-testid={`block-name-${block.id}`}
|
||||
>
|
||||
<TextRenderer
|
||||
value={beautifyString(block.name).replace(
|
||||
@@ -214,7 +218,10 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
truncateLengthLimit={45}
|
||||
/>
|
||||
</span>
|
||||
<span className="block break-all text-xs font-normal text-gray-500">
|
||||
<span
|
||||
className="block break-all text-xs font-normal text-gray-500"
|
||||
data-testid={`block-description-${block.id}`}
|
||||
>
|
||||
<TextRenderer
|
||||
value={block.description}
|
||||
truncateLengthLimit={165}
|
||||
@@ -224,6 +231,7 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
<div
|
||||
className="flex flex-shrink-0 items-center gap-1"
|
||||
data-id={`block-tooltip-${block.id}`}
|
||||
data-testid={`block-add`}
|
||||
>
|
||||
<PlusIcon className="h-6 w-6 rounded-lg bg-gray-200 stroke-black stroke-[0.5px] p-1" />
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<IconSave />
|
||||
</Button>
|
||||
@@ -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"
|
||||
maxLength={100}
|
||||
/>
|
||||
<Label htmlFor="description">Description</Label>
|
||||
@@ -125,6 +128,7 @@ export const SaveControl = ({
|
||||
value={agentDescription}
|
||||
onChange={(e) => onDescriptionChange(e.target.value)}
|
||||
data-id="save-control-description-input"
|
||||
data-testid="save-control-description-input"
|
||||
maxLength={500}
|
||||
/>
|
||||
{agentMeta?.version && (
|
||||
@@ -136,6 +140,7 @@ export const SaveControl = ({
|
||||
className="col-span-3"
|
||||
value={agentMeta?.version || "-"}
|
||||
disabled
|
||||
data-testid="save-control-version-output"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -146,6 +151,7 @@ export const SaveControl = ({
|
||||
className="w-full"
|
||||
onClick={handleSave}
|
||||
data-id="save-control-save-agent"
|
||||
data-testid="save-control-save-agent-button"
|
||||
>
|
||||
Save {getType()}
|
||||
</Button>
|
||||
|
||||
@@ -34,6 +34,7 @@ export function NavBarButtons({ className }: { className?: string }) {
|
||||
<Link
|
||||
key={button.href}
|
||||
href={button.href}
|
||||
data-testid={`${button.text.toLowerCase()}-nav-link`}
|
||||
className={cn(
|
||||
className,
|
||||
"flex items-center gap-2 rounded-xl p-3",
|
||||
@@ -49,6 +50,7 @@ export function NavBarButtons({ className }: { className?: string }) {
|
||||
{isCloud ? (
|
||||
<Link
|
||||
href="/marketplace"
|
||||
data-testid="marketplace-nav-link"
|
||||
className={cn(
|
||||
className,
|
||||
"flex items-center gap-2 rounded-xl p-3",
|
||||
@@ -61,6 +63,7 @@ export function NavBarButtons({ className }: { className?: string }) {
|
||||
</Link>
|
||||
) : (
|
||||
<MarketPopup
|
||||
data-testid="marketplace-nav-link"
|
||||
className={cn(
|
||||
className,
|
||||
"flex items-center gap-2 rounded-xl p-3 text-muted-foreground hover:text-foreground",
|
||||
|
||||
@@ -40,7 +40,11 @@ export function InputBlock({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{placeholder_values.map((placeholder, index) => (
|
||||
<SelectItem key={index} value={placeholder.toString()}>
|
||||
<SelectItem
|
||||
key={index}
|
||||
value={placeholder.toString()}
|
||||
data-testid={`run-dialog-input-${name}-${placeholder.toString()}`}
|
||||
>
|
||||
{placeholder.toString()}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -49,6 +53,7 @@ export function InputBlock({
|
||||
) : (
|
||||
<Input
|
||||
id={`${id}-Value`}
|
||||
data-testid={`run-dialog-input-${name}`}
|
||||
value={value}
|
||||
onChange={(e) => onInputChange(id, "value", e.target.value)}
|
||||
placeholder={placeholder_values?.[0]?.toString() || "Enter value"}
|
||||
|
||||
@@ -72,6 +72,7 @@ export function RunnerInputUI({
|
||||
</div>
|
||||
<DialogFooter className="px-6 py-4">
|
||||
<Button
|
||||
data-testid="run-dialog-run-button"
|
||||
onClick={scheduledInput ? handleSchedule : handleRun}
|
||||
className="px-8 py-2 text-lg"
|
||||
disabled={scheduledInput ? isScheduling : isRunning}
|
||||
|
||||
222
autogpt_platform/frontend/src/tests/build.spec.ts
Normal file
222
autogpt_platform/frontend/src/tests/build.spec.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
// 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();
|
||||
}
|
||||
// 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=.+"));
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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.connectBlockOutputToBlockInputViaDataId(
|
||||
"1-1-output-source",
|
||||
"1-2-input-target",
|
||||
);
|
||||
|
||||
// Fill in the input for the first block
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
block1.id,
|
||||
"Enter input",
|
||||
"Test Value",
|
||||
"1",
|
||||
);
|
||||
|
||||
// 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 buildPage.waitForSaveButton();
|
||||
|
||||
// Ensure the run button is enabled
|
||||
await test.expect(buildPage.isRunButtonEnabled()).resolves.toBeTruthy();
|
||||
|
||||
// Run the agent
|
||||
await buildPage.runAgent();
|
||||
|
||||
// Wait for processing to complete by checking the completion badge
|
||||
await buildPage.waitForCompletionBadge();
|
||||
|
||||
// Get the first completion badge and verify it's visible
|
||||
await test
|
||||
.expect(buildPage.isCompletionBadgeVisible())
|
||||
.resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
test("user can build an agent with inputs and output blocks", async ({
|
||||
page,
|
||||
}) => {
|
||||
// simple caluclator to double input and output it
|
||||
|
||||
// load the pages and prep
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
|
||||
// find the blocks we want
|
||||
const blocks = await buildPage.getBlocks();
|
||||
const inputBlock = blocks.find((b) => b.name === "Agent Input");
|
||||
const outputBlock = blocks.find((b) => b.name === "Agent Output");
|
||||
const calculatorBlock = blocks.find((b) => b.name === "Calculator");
|
||||
if (!inputBlock || !outputBlock || !calculatorBlock) {
|
||||
throw new Error("Input or output block not found");
|
||||
}
|
||||
|
||||
// add the blocks
|
||||
await buildPage.addBlock(inputBlock);
|
||||
await buildPage.addBlock(outputBlock);
|
||||
await buildPage.addBlock(calculatorBlock);
|
||||
await buildPage.closeBlocksPanel();
|
||||
await test.expect(buildPage.hasBlock(inputBlock)).resolves.toBeTruthy();
|
||||
await test.expect(buildPage.hasBlock(outputBlock)).resolves.toBeTruthy();
|
||||
await test
|
||||
.expect(buildPage.hasBlock(calculatorBlock))
|
||||
.resolves.toBeTruthy();
|
||||
|
||||
await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
inputBlock.id,
|
||||
"Result",
|
||||
calculatorBlock.id,
|
||||
"A",
|
||||
);
|
||||
await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
inputBlock.id,
|
||||
"Result",
|
||||
calculatorBlock.id,
|
||||
"B",
|
||||
);
|
||||
await buildPage.connectBlockOutputToBlockInputViaName(
|
||||
calculatorBlock.id,
|
||||
"Result",
|
||||
outputBlock.id,
|
||||
"Value",
|
||||
);
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
inputBlock.id,
|
||||
"Enter Name",
|
||||
"Value",
|
||||
);
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
outputBlock.id,
|
||||
"Enter Name",
|
||||
"Doubled",
|
||||
);
|
||||
await buildPage.selectBlockInputValue(
|
||||
calculatorBlock.id,
|
||||
"Operation",
|
||||
"Add",
|
||||
);
|
||||
await buildPage.saveAgent(
|
||||
"Input and Output Blocks Test",
|
||||
"Testing input and output blocks",
|
||||
);
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
await buildPage.runAgent();
|
||||
await buildPage.fillRunDialog({
|
||||
Value: "10",
|
||||
});
|
||||
await buildPage.clickRunDialogRunButton();
|
||||
await buildPage.waitForCompletionBadge();
|
||||
await test
|
||||
.expect(buildPage.isCompletionBadgeVisible())
|
||||
.resolves.toBeTruthy();
|
||||
});
|
||||
});
|
||||
299
autogpt_platform/frontend/src/tests/pages/build.page.ts
Normal file
299
autogpt_platform/frontend/src/tests/pages/build.page.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { ElementHandle, Locator, 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<void> {
|
||||
await this.page.getByRole("button", { name: "Skip Tutorial" }).click();
|
||||
}
|
||||
|
||||
async openBlocksPanel(): Promise<void> {
|
||||
if (
|
||||
!(await this.page.getByTestId("blocks-control-blocks-label").isVisible())
|
||||
) {
|
||||
await this.page.getByTestId("blocks-control-blocks-button").click();
|
||||
}
|
||||
}
|
||||
|
||||
async closeBlocksPanel(): Promise<void> {
|
||||
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<void> {
|
||||
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<Block[]> {
|
||||
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<void> {
|
||||
console.log(`adding block ${block.id} ${block.name} to agent`);
|
||||
await this.page.getByTestId(`block-name-${block.id}`).click();
|
||||
}
|
||||
|
||||
async isRFNodeVisible(nodeId: string): Promise<boolean> {
|
||||
return await this.page.getByTestId(`rf__node-${nodeId}`).isVisible();
|
||||
}
|
||||
|
||||
async hasBlock(block: Block): Promise<boolean> {
|
||||
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<string[]> {
|
||||
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 getBlockOutputs(blockId: string): Promise<string[]> {
|
||||
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 build_block_selector(
|
||||
blockId: string,
|
||||
dataId?: string,
|
||||
): Promise<string> {
|
||||
let selector = dataId
|
||||
? `[data-id="${dataId}"] [data-blockid="${blockId}"]`
|
||||
: `[data-blockid="${blockId}"]`;
|
||||
return selector;
|
||||
}
|
||||
|
||||
async getBlockById(blockId: string, dataId?: string): Promise<Locator> {
|
||||
return await this.page.locator(
|
||||
await this.build_block_selector(blockId, dataId),
|
||||
);
|
||||
}
|
||||
|
||||
// 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<void> {
|
||||
const block = await this.getBlockById(blockId, dataId);
|
||||
const input = await block.getByPlaceholder(placeholder);
|
||||
await input.fill(value);
|
||||
}
|
||||
|
||||
async selectBlockInputValue(
|
||||
blockId: string,
|
||||
inputName: string,
|
||||
value: string,
|
||||
dataId?: string,
|
||||
): Promise<void> {
|
||||
// First get the button that opens the dropdown
|
||||
const baseSelector = await this.build_block_selector(blockId, dataId);
|
||||
|
||||
// Find the combobox button within the input handle container
|
||||
const comboboxSelector = `${baseSelector} [data-id="input-handle-${inputName.toLowerCase()}"] button[role="combobox"]`;
|
||||
|
||||
try {
|
||||
// Click the combobox to open it
|
||||
await this.page.click(comboboxSelector);
|
||||
|
||||
// Wait a moment for the dropdown to open
|
||||
await this.page.waitForTimeout(100);
|
||||
|
||||
// Select the option from the dropdown
|
||||
// The actual selector for the option might need adjustment based on the dropdown structure
|
||||
await this.page.getByRole("option", { name: value }).click();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error selecting value "${value}" for input "${inputName}":`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async fillBlockInputByLabel(
|
||||
blockId: string,
|
||||
label: string,
|
||||
value: string,
|
||||
): Promise<void> {
|
||||
// throw new Error("Not implemented");
|
||||
const block = await this.getBlockById(blockId);
|
||||
const input = await block.getByLabel(label);
|
||||
await input.fill(value);
|
||||
}
|
||||
|
||||
async connectBlockOutputToBlockInputViaDataId(
|
||||
blockOutputId: string,
|
||||
blockInputId: string,
|
||||
): Promise<void> {
|
||||
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}"]`,
|
||||
);
|
||||
|
||||
await outputElement.dragTo(inputElement);
|
||||
} catch (error) {
|
||||
console.error("Error connecting block output to input:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async connectBlockOutputToBlockInputViaName(
|
||||
startBlockId: string,
|
||||
startBlockOutputName: string,
|
||||
endBlockId: string,
|
||||
endBlockInputName: string,
|
||||
startDataId?: string,
|
||||
endDataId?: string,
|
||||
): Promise<void> {
|
||||
const startBlockBase = await this.build_block_selector(
|
||||
startBlockId,
|
||||
startDataId,
|
||||
);
|
||||
const endBlockBase = await this.build_block_selector(endBlockId, endDataId);
|
||||
// Use descendant combinator to find test-id at any depth
|
||||
const startBlockOutputSelector = `${startBlockBase} [data-testid="output-handle-${startBlockOutputName.toLowerCase()}"]`;
|
||||
const endBlockInputSelector = `${endBlockBase} [data-testid="input-handle-${endBlockInputName.toLowerCase()}"]`;
|
||||
|
||||
// Log for debugging
|
||||
console.log("Start block selector:", startBlockOutputSelector);
|
||||
console.log("End block selector:", endBlockInputSelector);
|
||||
|
||||
await this.page
|
||||
.locator(startBlockOutputSelector)
|
||||
.dragTo(this.page.locator(endBlockInputSelector));
|
||||
}
|
||||
|
||||
async isLoaded(): Promise<boolean> {
|
||||
try {
|
||||
await this.page.waitForLoadState("networkidle", { timeout: 10_000 });
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async isRunButtonEnabled(): Promise<boolean> {
|
||||
const runButton = this.page.locator('[data-id="primary-action-run-agent"]');
|
||||
return await runButton.isEnabled();
|
||||
}
|
||||
|
||||
async runAgent(): Promise<void> {
|
||||
const runButton = this.page.locator('[data-id="primary-action-run-agent"]');
|
||||
await runButton.click();
|
||||
}
|
||||
|
||||
async fillRunDialog(inputs: Record<string, string>): Promise<void> {
|
||||
for (const [key, value] of Object.entries(inputs)) {
|
||||
await this.page.getByTestId(`run-dialog-input-${key}`).fill(value);
|
||||
}
|
||||
}
|
||||
async clickRunDialogRunButton(): Promise<void> {
|
||||
await this.page.getByTestId("run-dialog-run-button").click();
|
||||
}
|
||||
|
||||
async waitForCompletionBadge(): Promise<void> {
|
||||
await this.page.waitForSelector(
|
||||
'[data-id^="badge-"][data-id$="-COMPLETED"]',
|
||||
);
|
||||
}
|
||||
|
||||
async waitForSaveButton(): Promise<void> {
|
||||
await this.page.waitForSelector(
|
||||
'[data-testid="blocks-control-save-button"]:not([disabled])',
|
||||
);
|
||||
}
|
||||
|
||||
async isCompletionBadgeVisible(): Promise<boolean> {
|
||||
const completionBadge = this.page
|
||||
.locator('[data-id^="badge-"][data-id$="-COMPLETED"]')
|
||||
.first();
|
||||
return await completionBadge.isVisible();
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,15 @@ export class NavBar {
|
||||
}
|
||||
|
||||
async clickMonitorLink() {
|
||||
await this.page.getByTestId("monitor-link").click();
|
||||
await this.page.getByTestId("monitor-nav-link").click();
|
||||
}
|
||||
|
||||
async clickBuildLink() {
|
||||
await this.page.getByTestId("build-link").click();
|
||||
await this.page.getByTestId("build-nav-link").click();
|
||||
}
|
||||
|
||||
async clickMarketplaceLink() {
|
||||
await this.page.getByTestId("marketplace-link").click();
|
||||
await this.page.getByTestId("marketplace-nav-link").click();
|
||||
}
|
||||
|
||||
async getUserMenuButton() {
|
||||
|
||||
Reference in New Issue
Block a user