-
Notifications
You must be signed in to change notification settings - Fork 23
todo utils, helpers and specs #387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||
| import type { Page } from "@playwright/test"; | ||||||||
|
|
||||||||
| export async function waitForTodoSheet(page: Page, timeout = 5000): Promise<void> { | ||||||||
| await page.getByRole("heading", { name: "Todo Items" }).waitFor({ timeout }); | ||||||||
| } | ||||||||
|
|
||||||||
| export async function openTodoListViaUI(page: Page): Promise<void> { | ||||||||
| await page.getByRole("button", { name: "Show todo list" }).click(); | ||||||||
| await waitForTodoSheet(page); | ||||||||
| } | ||||||||
|
|
||||||||
| export async function openTodoListViaKeyboard(page: Page): Promise<void> { | ||||||||
| await page.keyboard.press("Shift+T"); | ||||||||
| await page.keyboard.press("S"); | ||||||||
| await waitForTodoSheet(page); | ||||||||
| } | ||||||||
|
|
||||||||
| export async function openTodoListViaCommandPalette(page: Page): Promise<void> { | ||||||||
| const isMac = process.platform === "darwin"; | ||||||||
| const modifier = isMac ? "Meta" : "Control"; | ||||||||
|
|
||||||||
| await page.keyboard.press(`${modifier}+K`); | ||||||||
| await page.getByPlaceholder(/Type a command/i).waitFor(); | ||||||||
| await page.getByRole("option", { name: /Show Todos/i }).click(); | ||||||||
| await waitForTodoSheet(page); | ||||||||
| } | ||||||||
|
|
||||||||
| export async function openAddTodoFormViaKeyboard(page: Page): Promise<void> { | ||||||||
| // Shift + T + N | ||||||||
| await page.keyboard.press("Shift+T"); | ||||||||
| await page.keyboard.press("N"); | ||||||||
| await page.getByRole("heading", { name: "Add Todo" }).waitFor(); | ||||||||
| } | ||||||||
|
|
||||||||
| export async function openAddTodoFormViaCommandPalette(page: Page): Promise<void> { | ||||||||
| const isMac = process.platform === "darwin"; | ||||||||
| const modifier = isMac ? "Meta" : "Control"; | ||||||||
|
|
||||||||
| await page.keyboard.press(`${modifier}+K`); | ||||||||
| await page.getByPlaceholder(/Type a command/i).waitFor(); | ||||||||
| await page.getByRole("option", { name: /Add Todo/i }).click(); | ||||||||
| await page.getByRole("heading", { name: "Add Todo" }).waitFor(); | ||||||||
| } | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||
| import type { Page } from "@playwright/test"; | ||||||||
|
|
||||||||
| import { Form } from "../components"; | ||||||||
| import { BaseComponent } from "../components/base"; | ||||||||
| import { waitForElementVisible } from "../helpers"; | ||||||||
|
|
||||||||
| export class TodoCreateFormSection extends BaseComponent { | ||||||||
| private form: Form; | ||||||||
|
|
||||||||
| constructor(page: Page) { | ||||||||
| super(page); | ||||||||
| this.form = new Form(page); | ||||||||
| } | ||||||||
|
|
||||||||
| async waitForLoad(options?: { timeout?: number }): Promise<void> { | ||||||||
| await waitForElementVisible( | ||||||||
| this.page.getByRole("heading", { name: "Add Todo" }), | ||||||||
| options | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
| async fillTodoFields(fields: { | ||||||||
| title: string; | ||||||||
| description?: string; | ||||||||
| priority?: "normal" | "important" | "urgent" | "critical"; | ||||||||
| dueDate?: Date; | ||||||||
| }): Promise<void> { | ||||||||
| await this.form.fillFields({ Title: fields.title }); | ||||||||
|
|
||||||||
| if (fields.description) { | ||||||||
| await this.page.getByPlaceholder(/Enter todo description/i).fill(fields.description); | ||||||||
| } | ||||||||
|
|
||||||||
| if (fields.priority) { | ||||||||
| await this.page.getByRole("combobox", { name: /priority/i }).click(); | ||||||||
| await this.page.getByRole("option", { name: fields.priority, exact: true }).click(); | ||||||||
| } | ||||||||
|
|
||||||||
| if (fields.dueDate) { | ||||||||
| const dateButton = this.page.getByRole("button", { name: /due date/i }); | ||||||||
| await dateButton.click(); | ||||||||
| // Select date from calendar picker | ||||||||
| const formattedDate = fields.dueDate.getDate().toString(); | ||||||||
| await this.page.getByRole("gridcell", { name: formattedDate, exact: true }).click(); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| async setCreateMore(checked: boolean): Promise<void> { | ||||||||
| const checkbox = this.page.getByRole("checkbox", { name: /create more/i }); | ||||||||
| const isChecked = await checkbox.isChecked(); | ||||||||
| if (checked !== isChecked) { | ||||||||
| await checkbox.click(); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| async submit(): Promise<void> { | ||||||||
| await this.form.submit("Add todo"); | ||||||||
| } | ||||||||
|
|
||||||||
| async createTodo(fields: { | ||||||||
| title: string; | ||||||||
| description?: string; | ||||||||
| priority?: "normal" | "important" | "urgent" | "critical"; | ||||||||
| dueDate?: Date; | ||||||||
| createMore?: boolean; | ||||||||
| }): Promise<void> { | ||||||||
| await this.fillTodoFields(fields); | ||||||||
| if (fields.createMore) { | ||||||||
| await this.setCreateMore(true); | ||||||||
| } | ||||||||
| await this.submit(); | ||||||||
| } | ||||||||
| } | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,75 @@ | ||||||||
| import type { Page } from "@playwright/test"; | ||||||||
|
|
||||||||
| import { Form } from "../components"; | ||||||||
| import { BaseComponent } from "../components/base"; | ||||||||
| import { waitForElementVisible } from "../helpers"; | ||||||||
|
|
||||||||
| export class TodoEditFormSection extends BaseComponent { | ||||||||
| private form: Form; | ||||||||
|
|
||||||||
| constructor(page: Page) { | ||||||||
| super(page); | ||||||||
| this.form = new Form(page); | ||||||||
| } | ||||||||
|
|
||||||||
| async waitForLoad(options?: { timeout?: number }): Promise<void> { | ||||||||
| await waitForElementVisible( | ||||||||
| this.page.getByRole("heading", { name: "Edit Todo" }), | ||||||||
| options | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
| async fillTodoFields(fields: { | ||||||||
| title?: string; | ||||||||
| description?: string; | ||||||||
| priority?: "normal" | "important" | "urgent" | "critical"; | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we have a |
||||||||
| dueDate?: Date | null; | ||||||||
| }): Promise<void> { | ||||||||
| if (fields.title !== undefined) { | ||||||||
| const titleInput = this.page.getByLabel(/title/i); | ||||||||
| await titleInput.clear(); | ||||||||
| await titleInput.fill(fields.title); | ||||||||
| } | ||||||||
|
|
||||||||
| if (fields.description !== undefined) { | ||||||||
| const descInput = this.page.getByPlaceholder(/Enter todo description/i); | ||||||||
| await descInput.clear(); | ||||||||
| await descInput.fill(fields.description); | ||||||||
| } | ||||||||
|
|
||||||||
| if (fields.priority) { | ||||||||
| await this.page.getByRole("combobox", { name: /priority/i }).click(); | ||||||||
| await this.page.getByRole("option", { name: fields.priority, exact: true }).click(); | ||||||||
| } | ||||||||
|
|
||||||||
| if (fields.dueDate !== undefined) { | ||||||||
| if (fields.dueDate === null) { | ||||||||
| // Clear date | ||||||||
| const dateButton = this.page.getByRole("button", { name: /due date/i }); | ||||||||
| const clearButton = dateButton.locator('[aria-label="Clear"]'); | ||||||||
| if (await clearButton.isVisible()) { | ||||||||
| await clearButton.click(); | ||||||||
| } | ||||||||
| } else { | ||||||||
| const dateButton = this.page.getByRole("button", { name: /due date/i }); | ||||||||
| await dateButton.click(); | ||||||||
| const formattedDate = fields.dueDate.getDate().toString(); | ||||||||
| await this.page.getByRole("gridcell", { name: formattedDate, exact: true }).click(); | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| async submit(): Promise<void> { | ||||||||
| await this.form.submit("Update todo"); | ||||||||
| } | ||||||||
|
|
||||||||
| async updateTodo(fields: { | ||||||||
| title?: string; | ||||||||
| description?: string; | ||||||||
| priority?: "normal" | "important" | "urgent" | "critical"; | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the |
||||||||
| dueDate?: Date | null; | ||||||||
| }): Promise<void> { | ||||||||
| await this.fillTodoFields(fields); | ||||||||
| await this.submit(); | ||||||||
| } | ||||||||
| } | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import type { Locator, Page } from "@playwright/test"; | ||
|
|
||
| import { BaseComponent } from "../components/base"; | ||
| import { waitForElementVisible } from "../helpers"; | ||
|
|
||
| export class TodoSection extends BaseComponent { | ||
| constructor(page: Page) { | ||
| super(page); | ||
| } | ||
|
|
||
| async waitForLoad(options?: { timeout?: number }): Promise<void> { | ||
| await waitForElementVisible( | ||
| this.page.getByRole("heading", { name: "Todo Items" }), | ||
| options | ||
| ); | ||
| } | ||
|
|
||
| getTodoSheet(): Locator { | ||
| return this.page.getByRole("dialog").filter({ hasText: "Todo Items" }); | ||
| } | ||
|
|
||
| async isOpen(): Promise<boolean> { | ||
| return await this.getTodoSheet().isVisible(); | ||
| } | ||
|
|
||
| async close(): Promise<void> { | ||
| await this.page.keyboard.press("Escape"); | ||
| } | ||
|
|
||
| getAddTodoButton(): Locator { | ||
| return this.page.getByRole("button", { name: "Add Todo" }); | ||
| } | ||
|
|
||
| async clickAddTodo(): Promise<void> { | ||
| await this.getAddTodoButton().click(); | ||
| } | ||
|
|
||
| getTodoItems(): Locator { | ||
| return this.getTodoSheet().locator('[class*="group"]').filter({ hasText: /^(?!No todos found)/ }); | ||
| } | ||
|
|
||
| getTodoByTitle(title: string): Locator { | ||
| return this.getTodoSheet().locator('[class*="group"]').filter({ hasText: title }).first(); | ||
| } | ||
|
|
||
| async getTodoCount(): Promise<number> { | ||
| return await this.getTodoItems().count(); | ||
| } | ||
|
|
||
| getEmptyState(): Locator { | ||
| return this.getTodoSheet().getByText("No todos found"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,88 @@ | ||||||||
| import { expect, test } from "./fixtures"; | ||||||||
| import { waitForSuccessToast } from "./helpers"; | ||||||||
| import { openTodoListViaUI } from "./helpers/todo"; | ||||||||
| import { TodoCreateFormSection } from "./sections/todo-create-form-section"; | ||||||||
| import { TodoSection } from "./sections/todo-section"; | ||||||||
| import { USER_DEFAULT_PASSWORD, loginUser } from "./utils/auth"; | ||||||||
| import { createUser } from "./utils/db"; | ||||||||
| import { generateTitleOnly } from "./utils/todo"; | ||||||||
|
|
||||||||
| test.describe("@todo.complete TODO Complete/Uncomplete E2E Tests", () => { | ||||||||
| test("should mark TODO as complete", async ({ page, testConfig }) => { | ||||||||
| const testUser = await createUser(testConfig); | ||||||||
| await loginUser(page, { email: testUser.email, password: USER_DEFAULT_PASSWORD }); | ||||||||
|
|
||||||||
| const todoSection = new TodoSection(page); | ||||||||
| const createForm = new TodoCreateFormSection(page); | ||||||||
| const todoData = generateTitleOnly(); | ||||||||
|
|
||||||||
| await openTodoListViaUI(page); | ||||||||
| await todoSection.waitForLoad(); | ||||||||
| await todoSection.clickAddTodo(); | ||||||||
| await createForm.waitForLoad(); | ||||||||
| await createForm.createTodo(todoData); | ||||||||
| await waitForSuccessToast(page, "Todo added successfully"); | ||||||||
|
|
||||||||
| const todoItem = todoSection.getTodoByTitle(todoData.title); | ||||||||
| await todoItem.hover(); | ||||||||
| await todoItem.getByTitle("Mark as complete").click(); | ||||||||
|
|
||||||||
| await waitForSuccessToast(page, "marked as completed"); | ||||||||
| expect(await todoItem.getByText("Completed").isVisible()).toBe(true); | ||||||||
| }); | ||||||||
|
|
||||||||
| test("should mark completed TODO as incomplete", async ({ page, testConfig }) => { | ||||||||
| const testUser = await createUser(testConfig); | ||||||||
| await loginUser(page, { email: testUser.email, password: USER_DEFAULT_PASSWORD }); | ||||||||
|
|
||||||||
| const todoSection = new TodoSection(page); | ||||||||
| const createForm = new TodoCreateFormSection(page); | ||||||||
| const todoData = generateTitleOnly(); | ||||||||
|
|
||||||||
| await openTodoListViaUI(page); | ||||||||
| await todoSection.waitForLoad(); | ||||||||
| await todoSection.clickAddTodo(); | ||||||||
| await createForm.waitForLoad(); | ||||||||
| await createForm.createTodo(todoData); | ||||||||
| await waitForSuccessToast(page, "Todo added successfully"); | ||||||||
|
|
||||||||
| const todoItem = todoSection.getTodoByTitle(todoData.title); | ||||||||
| await todoItem.hover(); | ||||||||
| await todoItem.getByTitle("Mark as complete").click(); | ||||||||
| await waitForSuccessToast(page, "marked as completed"); | ||||||||
|
|
||||||||
| await todoItem.hover(); | ||||||||
| await todoItem.getByTitle("Mark as incomplete").click(); | ||||||||
|
|
||||||||
| await waitForSuccessToast(page, "marked as incomplete"); | ||||||||
| expect(await todoItem.getByText("Completed").isVisible()).toBe(false); | ||||||||
| }); | ||||||||
|
|
||||||||
| test("should apply completed styling to completed TODO", async ({ page, testConfig }) => { | ||||||||
| const testUser = await createUser(testConfig); | ||||||||
| await loginUser(page, { email: testUser.email, password: USER_DEFAULT_PASSWORD }); | ||||||||
|
|
||||||||
| const todoSection = new TodoSection(page); | ||||||||
| const createForm = new TodoCreateFormSection(page); | ||||||||
| const todoData = generateTitleOnly(); | ||||||||
|
|
||||||||
| await openTodoListViaUI(page); | ||||||||
| await todoSection.waitForLoad(); | ||||||||
| await todoSection.clickAddTodo(); | ||||||||
| await createForm.waitForLoad(); | ||||||||
| await createForm.createTodo(todoData); | ||||||||
| await waitForSuccessToast(page, "Todo added successfully"); | ||||||||
|
|
||||||||
| const todoItem = todoSection.getTodoByTitle(todoData.title); | ||||||||
| await todoItem.hover(); | ||||||||
| await todoItem.getByTitle("Mark as complete").click(); | ||||||||
| await waitForSuccessToast(page, "marked as completed"); | ||||||||
|
|
||||||||
| // Check for strikethrough on title | ||||||||
| const title = todoItem.locator("h4").first(); | ||||||||
| await expect(title).toHaveClass(/line-through/); | ||||||||
|
|
||||||||
| // Check for reduced opacity on todo item | ||||||||
| await expect(todoItem).toHaveClass(/opacity-75/); | ||||||||
| }); | ||||||||
| }); | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.