Compare commits

...

2 Commits

Author SHA1 Message Date
Abhimanyu Yadav
bea287957d Merge branch 'dev' into abhimanyuyadav/open-2604-create-e2e-tests-for-settings-page 2025-08-11 09:36:22 +05:30
abhi1992002
84d7b05cd2 feat(frontend): enhance SettingsForm with data-testid attributes for improved testing
Added data-testid attributes to various input fields and switches in the SettingsForm component to facilitate end-to-end testing. This includes email, password, confirmation password fields, and notification switches for agent run, block execution failures, continuous agent errors, zero balance, low balance, daily summary, weekly summary, and monthly summary. Also added a data-testid for the cancel button.
2025-08-10 12:14:30 +05:30
3 changed files with 285 additions and 1 deletions

View File

@@ -44,7 +44,7 @@ export const SettingsForm = ({
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} type="email" />
<Input {...field} type="email" data-testid="settings-email" />
</FormControl>
<FormMessage />
</FormItem>
@@ -62,6 +62,7 @@ export const SettingsForm = ({
{...field}
type="password"
placeholder="************"
data-testid="settings-password"
/>
</FormControl>
<FormMessage />
@@ -80,6 +81,7 @@ export const SettingsForm = ({
{...field}
type="password"
placeholder="************"
data-testid="settings-confirm-password"
/>
</FormControl>
<FormMessage />
@@ -117,6 +119,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-agent-run"
/>
</FormControl>
</FormItem>
@@ -141,6 +144,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-block-execution-failed"
/>
</FormControl>
</FormItem>
@@ -164,6 +168,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-continuous-agent-error"
/>
</FormControl>
</FormItem>
@@ -193,6 +198,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-zero-balance"
/>
</FormControl>
</FormItem>
@@ -216,6 +222,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-low-balance"
/>
</FormControl>
</FormItem>
@@ -243,6 +250,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-daily-summary"
/>
</FormControl>
</FormItem>
@@ -264,6 +272,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-weekly-summary"
/>
</FormControl>
</FormItem>
@@ -285,6 +294,7 @@ export const SettingsForm = ({
<Switch
checked={field.value}
onCheckedChange={field.onChange}
data-testid="settings-notify-on-monthly-summary"
/>
</FormControl>
</FormItem>
@@ -300,6 +310,7 @@ export const SettingsForm = ({
type="button"
onClick={onCancel}
disabled={form.formState.isSubmitting}
data-testid="settings-cancel"
>
Cancel
</Button>

View File

@@ -0,0 +1,182 @@
import { expect, Locator, Page } from "@playwright/test";
import { BasePage } from "./base.page";
import { getSelectors } from "../utils/selectors";
export async function getSwitchState(toggle: Locator): Promise<boolean> {
const ariaChecked = await toggle.getAttribute("aria-checked");
if (ariaChecked === "true") return true;
if (ariaChecked === "false") return false;
const dataState = await toggle.getAttribute("data-state");
if (dataState === "checked") return true;
if (dataState === "unchecked") return false;
const dataChecked = await toggle.getAttribute("data-checked");
return dataChecked !== null;
}
type ToggleId =
| "settings-notify-on-agent-run"
| "settings-notify-on-block-execution-failed"
| "settings-notify-on-continuous-agent-error"
| "settings-notify-on-zero-balance"
| "settings-notify-on-low-balance"
| "settings-notify-on-daily-summary"
| "settings-notify-on-weekly-summary"
| "settings-notify-on-monthly-summary";
export const TOGGLE_IDS: ReadonlyArray<ToggleId> = [
"settings-notify-on-agent-run",
"settings-notify-on-block-execution-failed",
"settings-notify-on-continuous-agent-error",
"settings-notify-on-zero-balance",
"settings-notify-on-low-balance",
"settings-notify-on-daily-summary",
"settings-notify-on-weekly-summary",
"settings-notify-on-monthly-summary",
];
export class SettingsPage extends BasePage {
static readonly path = "/profile/settings";
constructor(page: Page) {
super(page);
}
async goto(): Promise<void> {
await this.page.goto(SettingsPage.path);
await this.isLoaded();
}
async isLoaded(): Promise<boolean> {
try {
await this.page.waitForLoadState("domcontentloaded");
const header = this.page.getByRole("heading", { name: "My account" });
const email = this.getEmailInput();
await Promise.all([
header.waitFor({ state: "visible" }),
email.waitFor({ state: "visible" }),
]);
return true;
} catch {
return false;
}
}
getEmailInput(): Locator {
return this.page.getByTestId("settings-email");
}
getPasswordInput(): Locator {
return this.page.getByTestId("settings-password");
}
getConfirmPasswordInput(): Locator {
return this.page.getByTestId("settings-confirm-password");
}
getToggle(id: ToggleId): Locator {
return this.page.getByTestId(id);
}
getCancelButton(): Locator {
return this.page.getByTestId("settings-cancel");
}
getSaveButton(): Locator {
return this.page.getByRole("button", { name: /Save changes|Saving\.\.\./ });
}
async setEmail(value: string): Promise<void> {
const input = this.getEmailInput();
await input.waitFor({ state: "visible" });
await input.fill(value);
await expect(input).toHaveValue(value);
}
async setPassword(value: string): Promise<void> {
const input = this.getPasswordInput();
await input.waitFor({ state: "visible" });
await input.fill(value);
await expect(input).toHaveValue(value);
}
async setConfirmPassword(value: string): Promise<void> {
const input = this.getConfirmPasswordInput();
await input.waitFor({ state: "visible" });
await input.fill(value);
await expect(input).toHaveValue(value);
}
private async getSwitchState(toggle: Locator): Promise<boolean> {
const ariaChecked = await toggle.getAttribute("aria-checked");
if (ariaChecked === "true") return true;
if (ariaChecked === "false") return false;
const dataState = await toggle.getAttribute("data-state");
if (dataState === "checked") return true;
if (dataState === "unchecked") return false;
const dataChecked = await toggle.getAttribute("data-checked");
return dataChecked !== null;
}
private async setSwitchState(toggle: Locator, desired: boolean): Promise<void> {
await toggle.waitFor({ state: "visible" });
const current = await this.getSwitchState(toggle);
if (current !== desired) {
await toggle.click();
await expect
.poll(async () => this.getSwitchState(toggle))
.toBe(desired);
}
}
async toggle(id: ToggleId): Promise<void> {
await this.getToggle(id).click();
}
async enable(id: ToggleId): Promise<void> {
await this.setSwitchState(this.getToggle(id), true);
}
async disable(id: ToggleId): Promise<void> {
await this.setSwitchState(this.getToggle(id), false);
}
async cancelChanges(): Promise<void> {
const btn = this.getCancelButton();
await btn.waitFor({ state: "visible" });
await btn.click();
await this.getEmailInput().waitFor({ state: "visible" });
}
async saveChanges(): Promise<void> {
const btn = this.getSaveButton();
await btn.waitFor({ state: "visible" });
await btn.click();
await this.waitForSaveComplete();
}
async waitForSaveComplete(): Promise<void> {
const { getText } = getSelectors(this.page);
const toast = getText("Successfully updated settings");
await Promise.race([
toast.waitFor({ state: "visible" }),
expect(this.getSaveButton()).toHaveText("Save changes"),
]);
}
async getEmailValue(): Promise<string> {
return this.getEmailInput().inputValue();
}
async expectValidationError(text: string | RegExp): Promise<void> {
const { getText } = getSelectors(this.page);
await expect(getText(text)).toBeVisible();
}
}
export async function navigateToSettings(page: Page): Promise<SettingsPage> {
const settings = new SettingsPage(page);
await settings.goto();
return settings;
}

View File

@@ -0,0 +1,91 @@
import test, { expect } from "@playwright/test";
import { LoginPage } from "./pages/login.page";
import { TEST_CREDENTIALS } from "./credentials";
import { hasFieldValue, hasUrl } from "./utils/assertion";
import { navigateToSettings, getSwitchState, TOGGLE_IDS } from "./pages/settings.page";
test.describe("Settings", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/login");
const loginPage = new LoginPage(page);
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
await hasUrl(page, "/marketplace");
});
test("settings page redirects to login when not authenticated", async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("/profile/settings");
await hasUrl(page, /\/login/);
await context.close();
});
test("user can successfully update settings", async ({ page }) => {
const settings = await navigateToSettings(page);
for (const id of TOGGLE_IDS) {
const state = await getSwitchState(settings.getToggle(id));
expect.soft(state, `Initial state of ${id}`).toBe(true);
}
// Turn all ON, change email/password, save
for (const id of TOGGLE_IDS) {
await settings.disable(id);
}
const tempEmail = `temp+e2e@example.com`;
await settings.setEmail(tempEmail);
await settings.setPassword("temporarypassword123");
await settings.setConfirmPassword("temporarypassword123");
await settings.saveChanges();
// Reload and verify change email/password and OFF persisted
await settings.goto();
for (const id of TOGGLE_IDS) {
const state = await getSwitchState(settings.getToggle(id));
expect.soft(state, `Persisted OFF state of ${id}`).toBe(false);
}
await hasFieldValue(settings.getEmailInput(), tempEmail);
await hasFieldValue(settings.getPasswordInput(), "temporarypassword123");
// Restore original test email and password
await settings.setEmail(TEST_CREDENTIALS.email);
await settings.setPassword(TEST_CREDENTIALS.password);
await settings.setConfirmPassword(TEST_CREDENTIALS.password);
for (const id of TOGGLE_IDS) {
await settings.enable(id);
}
await settings.saveChanges();
// Reload and verify change email/password and ON persisted
await settings.goto();
for (const id of TOGGLE_IDS) {
const state = await getSwitchState(settings.getToggle(id));
expect.soft(state, `Persisted ON state of ${id}`).toBe(true);
}
await hasFieldValue(settings.getEmailInput(), TEST_CREDENTIALS.email);
await hasFieldValue(settings.getPasswordInput(), TEST_CREDENTIALS.password);
});
test("user can cancel changes", async ({ page }) => {
const settings = await navigateToSettings(page);
const initialEmail = await settings.getEmailValue();
await settings.setEmail("settings+cancel@example.com");
await settings.cancelChanges();
await hasFieldValue(settings.getEmailInput(), initialEmail);
});
test("settings form shows validation errors for invalid inputs", async ({ page }) => {
const settings = await navigateToSettings(page);
await settings.setEmail("invalid-email");
await settings.setPassword("short");
await settings.setConfirmPassword("short");
await settings.saveChanges();
await settings.expectValidationError("Invalid email");
await settings.expectValidationError("String must contain at least 12 character(s)");
});
});