mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
Merge remote-tracking branch 'origin/main' into category
This commit is contained in:
78
apps/ui/tests/utils/components/autocomplete.ts
Normal file
78
apps/ui/tests/utils/components/autocomplete.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { waitForElement, waitForElementHidden } from "../core/waiting";
|
||||
|
||||
/**
|
||||
* Check if the category autocomplete dropdown is visible
|
||||
*/
|
||||
export async function isCategoryAutocompleteListVisible(
|
||||
page: Page
|
||||
): Promise<boolean> {
|
||||
const list = page.locator('[data-testid="category-autocomplete-list"]');
|
||||
return await list.isVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the category autocomplete dropdown to be visible
|
||||
*/
|
||||
export async function waitForCategoryAutocompleteList(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "category-autocomplete-list", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the category autocomplete dropdown to be hidden
|
||||
*/
|
||||
export async function waitForCategoryAutocompleteListHidden(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<void> {
|
||||
await waitForElementHidden(page, "category-autocomplete-list", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click a category option in the autocomplete dropdown
|
||||
*/
|
||||
export async function clickCategoryOption(
|
||||
page: Page,
|
||||
categoryName: string
|
||||
): Promise<void> {
|
||||
const optionTestId = `category-option-${categoryName
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")}`;
|
||||
const option = page.locator(`[data-testid="${optionTestId}"]`);
|
||||
await option.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category option element by name
|
||||
*/
|
||||
export async function getCategoryOption(
|
||||
page: Page,
|
||||
categoryName: string
|
||||
): Promise<Locator> {
|
||||
const optionTestId = `category-option-${categoryName
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")}`;
|
||||
return page.locator(`[data-testid="${optionTestId}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the "Create new" option for a category that doesn't exist
|
||||
*/
|
||||
export async function clickCreateNewCategoryOption(
|
||||
page: Page
|
||||
): Promise<void> {
|
||||
const option = page.locator('[data-testid="category-option-create-new"]');
|
||||
await option.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "Create new" option element for categories
|
||||
*/
|
||||
export async function getCreateNewCategoryOption(
|
||||
page: Page
|
||||
): Promise<Locator> {
|
||||
return page.locator('[data-testid="category-option-create-new"]');
|
||||
}
|
||||
200
apps/ui/tests/utils/components/dialogs.ts
Normal file
200
apps/ui/tests/utils/components/dialogs.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { clickElement } from "../core/interactions";
|
||||
import { waitForElement, waitForElementHidden } from "../core/waiting";
|
||||
|
||||
/**
|
||||
* Check if the add feature dialog is visible
|
||||
*/
|
||||
export async function isAddFeatureDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="add-feature-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the add context file dialog is visible
|
||||
*/
|
||||
export async function isAddContextDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="add-context-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the edit feature dialog is visible
|
||||
*/
|
||||
export async function isEditFeatureDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="edit-feature-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the edit feature dialog to be visible
|
||||
*/
|
||||
export async function waitForEditFeatureDialog(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "edit-feature-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edit feature description input/textarea element
|
||||
*/
|
||||
export async function getEditFeatureDescriptionInput(
|
||||
page: Page
|
||||
): Promise<Locator> {
|
||||
return page.locator('[data-testid="edit-feature-description"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the edit feature description field is a textarea
|
||||
*/
|
||||
export async function isEditFeatureDescriptionTextarea(
|
||||
page: Page
|
||||
): Promise<boolean> {
|
||||
const element = page.locator('[data-testid="edit-feature-description"]');
|
||||
const tagName = await element.evaluate((el) => el.tagName.toLowerCase());
|
||||
return tagName === "textarea";
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the edit dialog for a specific feature
|
||||
*/
|
||||
export async function openEditFeatureDialog(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<void> {
|
||||
await clickElement(page, `edit-feature-${featureId}`);
|
||||
await waitForEditFeatureDialog(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the edit feature description field
|
||||
*/
|
||||
export async function fillEditFeatureDescription(
|
||||
page: Page,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
const input = await getEditFeatureDescriptionInput(page);
|
||||
await input.fill(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the confirm edit feature button
|
||||
*/
|
||||
export async function confirmEditFeature(page: Page): Promise<void> {
|
||||
await clickElement(page, "confirm-edit-feature");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delete confirmation dialog
|
||||
*/
|
||||
export async function getDeleteConfirmationDialog(
|
||||
page: Page
|
||||
): Promise<Locator> {
|
||||
return page.locator('[data-testid="delete-confirmation-dialog"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the delete confirmation dialog is visible
|
||||
*/
|
||||
export async function isDeleteConfirmationDialogVisible(
|
||||
page: Page
|
||||
): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="delete-confirmation-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the delete confirmation dialog to appear
|
||||
*/
|
||||
export async function waitForDeleteConfirmationDialog(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "delete-confirmation-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the delete confirmation dialog to be hidden
|
||||
*/
|
||||
export async function waitForDeleteConfirmationDialogHidden(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<void> {
|
||||
await waitForElementHidden(page, "delete-confirmation-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the confirm delete button in the delete confirmation dialog
|
||||
*/
|
||||
export async function clickConfirmDeleteButton(page: Page): Promise<void> {
|
||||
await clickElement(page, "confirm-delete-button");
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the cancel delete button in the delete confirmation dialog
|
||||
*/
|
||||
export async function clickCancelDeleteButton(page: Page): Promise<void> {
|
||||
await clickElement(page, "cancel-delete-button");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the follow-up dialog is visible
|
||||
*/
|
||||
export async function isFollowUpDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="follow-up-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the follow-up dialog to be visible
|
||||
*/
|
||||
export async function waitForFollowUpDialog(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "follow-up-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the follow-up dialog to be hidden
|
||||
*/
|
||||
export async function waitForFollowUpDialogHidden(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<void> {
|
||||
await waitForElementHidden(page, "follow-up-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the confirm follow-up button in the follow-up dialog
|
||||
*/
|
||||
export async function clickConfirmFollowUp(page: Page): Promise<void> {
|
||||
await clickElement(page, "confirm-follow-up");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the project initialization dialog is visible
|
||||
*/
|
||||
export async function isProjectInitDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="project-init-dialog"]');
|
||||
return await dialog.isVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the project initialization dialog to appear
|
||||
*/
|
||||
export async function waitForProjectInitDialog(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "project-init-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the project initialization dialog
|
||||
*/
|
||||
export async function closeProjectInitDialog(page: Page): Promise<void> {
|
||||
const closeButton = page.locator('[data-testid="close-init-dialog"]');
|
||||
await closeButton.click();
|
||||
}
|
||||
104
apps/ui/tests/utils/components/modals.ts
Normal file
104
apps/ui/tests/utils/components/modals.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { waitForElement, waitForElementHidden } from "../core/waiting";
|
||||
|
||||
/**
|
||||
* Check if the agent output modal is visible
|
||||
*/
|
||||
export async function isAgentOutputModalVisible(page: Page): Promise<boolean> {
|
||||
const modal = page.locator('[data-testid="agent-output-modal"]');
|
||||
return await modal.isVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the agent output modal to be visible
|
||||
*/
|
||||
export async function waitForAgentOutputModal(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "agent-output-modal", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the agent output modal to be hidden
|
||||
*/
|
||||
export async function waitForAgentOutputModalHidden(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<void> {
|
||||
await waitForElementHidden(page, "agent-output-modal", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the modal title/description text to verify which feature's output is being shown
|
||||
*/
|
||||
export async function getAgentOutputModalDescription(
|
||||
page: Page
|
||||
): Promise<string | null> {
|
||||
const modal = page.locator('[data-testid="agent-output-modal"]');
|
||||
const description = modal
|
||||
.locator('[id="radix-\\:r.+\\:-description"]')
|
||||
.first();
|
||||
return await description.textContent().catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the dialog description content in the agent output modal
|
||||
*/
|
||||
export async function getOutputModalDescription(
|
||||
page: Page
|
||||
): Promise<string | null> {
|
||||
const modalDescription = page.locator(
|
||||
'[data-testid="agent-output-modal"] [data-slot="dialog-description"]'
|
||||
);
|
||||
return await modalDescription.textContent().catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the agent output modal description element
|
||||
*/
|
||||
export async function getAgentOutputModalDescriptionElement(
|
||||
page: Page
|
||||
): Promise<Locator> {
|
||||
return page.locator('[data-testid="agent-output-description"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the agent output modal description is scrollable
|
||||
*/
|
||||
export async function isAgentOutputDescriptionScrollable(
|
||||
page: Page
|
||||
): Promise<boolean> {
|
||||
const description = page.locator('[data-testid="agent-output-description"]');
|
||||
const scrollInfo = await description.evaluate((el) => {
|
||||
return {
|
||||
scrollHeight: el.scrollHeight,
|
||||
clientHeight: el.clientHeight,
|
||||
isScrollable: el.scrollHeight > el.clientHeight,
|
||||
};
|
||||
});
|
||||
return scrollInfo.isScrollable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scroll dimensions of the agent output modal description
|
||||
*/
|
||||
export async function getAgentOutputDescriptionScrollDimensions(
|
||||
page: Page
|
||||
): Promise<{
|
||||
scrollHeight: number;
|
||||
clientHeight: number;
|
||||
maxHeight: string;
|
||||
overflowY: string;
|
||||
}> {
|
||||
const description = page.locator('[data-testid="agent-output-description"]');
|
||||
return await description.evaluate((el) => {
|
||||
const style = window.getComputedStyle(el);
|
||||
return {
|
||||
scrollHeight: el.scrollHeight,
|
||||
clientHeight: el.clientHeight,
|
||||
maxHeight: style.maxHeight,
|
||||
overflowY: style.overflowY,
|
||||
};
|
||||
});
|
||||
}
|
||||
87
apps/ui/tests/utils/components/toasts.ts
Normal file
87
apps/ui/tests/utils/components/toasts.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { waitForElement } from "../core/waiting";
|
||||
|
||||
/**
|
||||
* Wait for a toast notification with specific text to appear
|
||||
*/
|
||||
export async function waitForToast(
|
||||
page: Page,
|
||||
text: string,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
const toast = page.locator(`[data-sonner-toast]:has-text("${text}")`).first();
|
||||
await toast.waitFor({
|
||||
timeout: options?.timeout ?? 5000,
|
||||
state: "visible",
|
||||
});
|
||||
return toast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an error toast to appear with specific text
|
||||
*/
|
||||
export async function waitForErrorToast(
|
||||
page: Page,
|
||||
titleText?: string,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
// Try multiple selectors for error toasts since Sonner versions may differ
|
||||
// 1. Try with data-type="error" attribute
|
||||
// 2. Fallback to any toast with the text (error styling might vary)
|
||||
const timeout = options?.timeout ?? 5000;
|
||||
|
||||
if (titleText) {
|
||||
// First try specific error type, then fallback to any toast with text
|
||||
const errorToast = page.locator(
|
||||
`[data-sonner-toast][data-type="error"]:has-text("${titleText}"), [data-sonner-toast]:has-text("${titleText}")`
|
||||
).first();
|
||||
await errorToast.waitFor({
|
||||
timeout,
|
||||
state: "visible",
|
||||
});
|
||||
return errorToast;
|
||||
} else {
|
||||
const errorToast = page.locator('[data-sonner-toast][data-type="error"]').first();
|
||||
await errorToast.waitFor({
|
||||
timeout,
|
||||
state: "visible",
|
||||
});
|
||||
return errorToast;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error toast is visible
|
||||
*/
|
||||
export async function isErrorToastVisible(
|
||||
page: Page,
|
||||
titleText?: string
|
||||
): Promise<boolean> {
|
||||
const toastSelector = titleText
|
||||
? `[data-sonner-toast][data-type="error"]:has-text("${titleText}")`
|
||||
: '[data-sonner-toast][data-type="error"]';
|
||||
|
||||
const toast = page.locator(toastSelector).first();
|
||||
return await toast.isVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a success toast to appear with specific text
|
||||
*/
|
||||
export async function waitForSuccessToast(
|
||||
page: Page,
|
||||
titleText?: string,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
// Sonner toasts use data-sonner-toast and data-type="success" for success toasts
|
||||
const toastSelector = titleText
|
||||
? `[data-sonner-toast][data-type="success"]:has-text("${titleText}")`
|
||||
: '[data-sonner-toast][data-type="success"]';
|
||||
|
||||
const toast = page.locator(toastSelector).first();
|
||||
await toast.waitFor({
|
||||
timeout: options?.timeout ?? 5000,
|
||||
state: "visible",
|
||||
});
|
||||
return toast;
|
||||
}
|
||||
Reference in New Issue
Block a user