github.com/minio/console@v1.4.1/web-app/tests-examples/demo-todo-app.spec.ts (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2023 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import { test, expect, type Page } from "@playwright/test";
    18  
    19  test.beforeEach(async ({ page }) => {
    20    await page.goto("https://demo.playwright.dev/todomvc");
    21  });
    22  
    23  const TODO_ITEMS = [
    24    "buy some cheese",
    25    "feed the cat",
    26    "book a doctors appointment",
    27  ];
    28  
    29  test.describe("New Todo", () => {
    30    test("should allow me to add todo items", async ({ page }) => {
    31      // create a new todo locator
    32      const newTodo = page.getByPlaceholder("What needs to be done?");
    33  
    34      // Create 1st todo.
    35      await newTodo.fill(TODO_ITEMS[0]);
    36      await newTodo.press("Enter");
    37  
    38      // Make sure the list only has one todo item.
    39      await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]);
    40  
    41      // Create 2nd todo.
    42      await newTodo.fill(TODO_ITEMS[1]);
    43      await newTodo.press("Enter");
    44  
    45      // Make sure the list now has two todo items.
    46      await expect(page.getByTestId("todo-title")).toHaveText([
    47        TODO_ITEMS[0],
    48        TODO_ITEMS[1],
    49      ]);
    50  
    51      await checkNumberOfTodosInLocalStorage(page, 2);
    52    });
    53  
    54    test("should clear text input field when an item is added", async ({
    55      page,
    56    }) => {
    57      // create a new todo locator
    58      const newTodo = page.getByPlaceholder("What needs to be done?");
    59  
    60      // Create one todo item.
    61      await newTodo.fill(TODO_ITEMS[0]);
    62      await newTodo.press("Enter");
    63  
    64      // Check that input is empty.
    65      await expect(newTodo).toBeEmpty();
    66      await checkNumberOfTodosInLocalStorage(page, 1);
    67    });
    68  
    69    test("should append new items to the bottom of the list", async ({
    70      page,
    71    }) => {
    72      // Create 3 items.
    73      await createDefaultTodos(page);
    74  
    75      // create a todo count locator
    76      const todoCount = page.getByTestId("todo-count");
    77  
    78      // Check test using different methods.
    79      await expect(page.getByText("3 items left")).toBeVisible();
    80      await expect(todoCount).toHaveText("3 items left");
    81      await expect(todoCount).toContainText("3");
    82      await expect(todoCount).toHaveText(/3/);
    83  
    84      // Check all items in one call.
    85      await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS);
    86      await checkNumberOfTodosInLocalStorage(page, 3);
    87    });
    88  });
    89  
    90  test.describe("Mark all as completed", () => {
    91    test.beforeEach(async ({ page }) => {
    92      await createDefaultTodos(page);
    93      await checkNumberOfTodosInLocalStorage(page, 3);
    94    });
    95  
    96    test.afterEach(async ({ page }) => {
    97      await checkNumberOfTodosInLocalStorage(page, 3);
    98    });
    99  
   100    test("should allow me to mark all items as completed", async ({ page }) => {
   101      // Complete all todos.
   102      await page.getByLabel("Mark all as complete").check();
   103  
   104      // Ensure all todos have 'completed' class.
   105      await expect(page.getByTestId("todo-item")).toHaveClass([
   106        "completed",
   107        "completed",
   108        "completed",
   109      ]);
   110      await checkNumberOfCompletedTodosInLocalStorage(page, 3);
   111    });
   112  
   113    test("should allow me to clear the complete state of all items", async ({
   114      page,
   115    }) => {
   116      const toggleAll = page.getByLabel("Mark all as complete");
   117      // Check and then immediately uncheck.
   118      await toggleAll.check();
   119      await toggleAll.uncheck();
   120  
   121      // Should be no completed classes.
   122      await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]);
   123    });
   124  
   125    test("complete all checkbox should update state when items are completed / cleared", async ({
   126      page,
   127    }) => {
   128      const toggleAll = page.getByLabel("Mark all as complete");
   129      await toggleAll.check();
   130      await expect(toggleAll).toBeChecked();
   131      await checkNumberOfCompletedTodosInLocalStorage(page, 3);
   132  
   133      // Uncheck first todo.
   134      const firstTodo = page.getByTestId("todo-item").nth(0);
   135      await firstTodo.getByRole("checkbox").uncheck();
   136  
   137      // Reuse toggleAll locator and make sure its not checked.
   138      await expect(toggleAll).not.toBeChecked();
   139  
   140      await firstTodo.getByRole("checkbox").check();
   141      await checkNumberOfCompletedTodosInLocalStorage(page, 3);
   142  
   143      // Assert the toggle all is checked again.
   144      await expect(toggleAll).toBeChecked();
   145    });
   146  });
   147  
   148  test.describe("Item", () => {
   149    test("should allow me to mark items as complete", async ({ page }) => {
   150      // create a new todo locator
   151      const newTodo = page.getByPlaceholder("What needs to be done?");
   152  
   153      // Create two items.
   154      for (const item of TODO_ITEMS.slice(0, 2)) {
   155        await newTodo.fill(item);
   156        await newTodo.press("Enter");
   157      }
   158  
   159      // Check first item.
   160      const firstTodo = page.getByTestId("todo-item").nth(0);
   161      await firstTodo.getByRole("checkbox").check();
   162      await expect(firstTodo).toHaveClass("completed");
   163  
   164      // Check second item.
   165      const secondTodo = page.getByTestId("todo-item").nth(1);
   166      await expect(secondTodo).not.toHaveClass("completed");
   167      await secondTodo.getByRole("checkbox").check();
   168  
   169      // Assert completed class.
   170      await expect(firstTodo).toHaveClass("completed");
   171      await expect(secondTodo).toHaveClass("completed");
   172    });
   173  
   174    test("should allow me to un-mark items as complete", async ({ page }) => {
   175      // create a new todo locator
   176      const newTodo = page.getByPlaceholder("What needs to be done?");
   177  
   178      // Create two items.
   179      for (const item of TODO_ITEMS.slice(0, 2)) {
   180        await newTodo.fill(item);
   181        await newTodo.press("Enter");
   182      }
   183  
   184      const firstTodo = page.getByTestId("todo-item").nth(0);
   185      const secondTodo = page.getByTestId("todo-item").nth(1);
   186      const firstTodoCheckbox = firstTodo.getByRole("checkbox");
   187  
   188      await firstTodoCheckbox.check();
   189      await expect(firstTodo).toHaveClass("completed");
   190      await expect(secondTodo).not.toHaveClass("completed");
   191      await checkNumberOfCompletedTodosInLocalStorage(page, 1);
   192  
   193      await firstTodoCheckbox.uncheck();
   194      await expect(firstTodo).not.toHaveClass("completed");
   195      await expect(secondTodo).not.toHaveClass("completed");
   196      await checkNumberOfCompletedTodosInLocalStorage(page, 0);
   197    });
   198  
   199    test("should allow me to edit an item", async ({ page }) => {
   200      await createDefaultTodos(page);
   201  
   202      const todoItems = page.getByTestId("todo-item");
   203      const secondTodo = todoItems.nth(1);
   204      await secondTodo.dblclick();
   205      await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue(
   206        TODO_ITEMS[1],
   207      );
   208      await secondTodo
   209        .getByRole("textbox", { name: "Edit" })
   210        .fill("buy some sausages");
   211      await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter");
   212  
   213      // Explicitly assert the new text value.
   214      await expect(todoItems).toHaveText([
   215        TODO_ITEMS[0],
   216        "buy some sausages",
   217        TODO_ITEMS[2],
   218      ]);
   219      await checkTodosInLocalStorage(page, "buy some sausages");
   220    });
   221  });
   222  
   223  test.describe("Editing", () => {
   224    test.beforeEach(async ({ page }) => {
   225      await createDefaultTodos(page);
   226      await checkNumberOfTodosInLocalStorage(page, 3);
   227    });
   228  
   229    test("should hide other controls when editing", async ({ page }) => {
   230      const todoItem = page.getByTestId("todo-item").nth(1);
   231      await todoItem.dblclick();
   232      await expect(todoItem.getByRole("checkbox")).not.toBeVisible();
   233      await expect(
   234        todoItem.locator("label", {
   235          hasText: TODO_ITEMS[1],
   236        }),
   237      ).not.toBeVisible();
   238      await checkNumberOfTodosInLocalStorage(page, 3);
   239    });
   240  
   241    test("should save edits on blur", async ({ page }) => {
   242      const todoItems = page.getByTestId("todo-item");
   243      await todoItems.nth(1).dblclick();
   244      await todoItems
   245        .nth(1)
   246        .getByRole("textbox", { name: "Edit" })
   247        .fill("buy some sausages");
   248      await todoItems
   249        .nth(1)
   250        .getByRole("textbox", { name: "Edit" })
   251        .dispatchEvent("blur");
   252  
   253      await expect(todoItems).toHaveText([
   254        TODO_ITEMS[0],
   255        "buy some sausages",
   256        TODO_ITEMS[2],
   257      ]);
   258      await checkTodosInLocalStorage(page, "buy some sausages");
   259    });
   260  
   261    test("should trim entered text", async ({ page }) => {
   262      const todoItems = page.getByTestId("todo-item");
   263      await todoItems.nth(1).dblclick();
   264      await todoItems
   265        .nth(1)
   266        .getByRole("textbox", { name: "Edit" })
   267        .fill("    buy some sausages    ");
   268      await todoItems
   269        .nth(1)
   270        .getByRole("textbox", { name: "Edit" })
   271        .press("Enter");
   272  
   273      await expect(todoItems).toHaveText([
   274        TODO_ITEMS[0],
   275        "buy some sausages",
   276        TODO_ITEMS[2],
   277      ]);
   278      await checkTodosInLocalStorage(page, "buy some sausages");
   279    });
   280  
   281    test("should remove the item if an empty text string was entered", async ({
   282      page,
   283    }) => {
   284      const todoItems = page.getByTestId("todo-item");
   285      await todoItems.nth(1).dblclick();
   286      await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("");
   287      await todoItems
   288        .nth(1)
   289        .getByRole("textbox", { name: "Edit" })
   290        .press("Enter");
   291  
   292      await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
   293    });
   294  
   295    test("should cancel edits on escape", async ({ page }) => {
   296      const todoItems = page.getByTestId("todo-item");
   297      await todoItems.nth(1).dblclick();
   298      await todoItems
   299        .nth(1)
   300        .getByRole("textbox", { name: "Edit" })
   301        .fill("buy some sausages");
   302      await todoItems
   303        .nth(1)
   304        .getByRole("textbox", { name: "Edit" })
   305        .press("Escape");
   306      await expect(todoItems).toHaveText(TODO_ITEMS);
   307    });
   308  });
   309  
   310  test.describe("Counter", () => {
   311    test("should display the current number of todo items", async ({ page }) => {
   312      // create a new todo locator
   313      const newTodo = page.getByPlaceholder("What needs to be done?");
   314  
   315      // create a todo count locator
   316      const todoCount = page.getByTestId("todo-count");
   317  
   318      await newTodo.fill(TODO_ITEMS[0]);
   319      await newTodo.press("Enter");
   320  
   321      await expect(todoCount).toContainText("1");
   322  
   323      await newTodo.fill(TODO_ITEMS[1]);
   324      await newTodo.press("Enter");
   325      await expect(todoCount).toContainText("2");
   326  
   327      await checkNumberOfTodosInLocalStorage(page, 2);
   328    });
   329  });
   330  
   331  test.describe("Clear completed button", () => {
   332    test.beforeEach(async ({ page }) => {
   333      await createDefaultTodos(page);
   334    });
   335  
   336    test("should display the correct text", async ({ page }) => {
   337      await page.locator(".todo-list li .toggle").first().check();
   338      await expect(
   339        page.getByRole("button", { name: "Clear completed" }),
   340      ).toBeVisible();
   341    });
   342  
   343    test("should remove completed items when clicked", async ({ page }) => {
   344      const todoItems = page.getByTestId("todo-item");
   345      await todoItems.nth(1).getByRole("checkbox").check();
   346      await page.getByRole("button", { name: "Clear completed" }).click();
   347      await expect(todoItems).toHaveCount(2);
   348      await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
   349    });
   350  
   351    test("should be hidden when there are no items that are completed", async ({
   352      page,
   353    }) => {
   354      await page.locator(".todo-list li .toggle").first().check();
   355      await page.getByRole("button", { name: "Clear completed" }).click();
   356      await expect(
   357        page.getByRole("button", { name: "Clear completed" }),
   358      ).toBeHidden();
   359    });
   360  });
   361  
   362  test.describe("Persistence", () => {
   363    test("should persist its data", async ({ page }) => {
   364      // create a new todo locator
   365      const newTodo = page.getByPlaceholder("What needs to be done?");
   366  
   367      for (const item of TODO_ITEMS.slice(0, 2)) {
   368        await newTodo.fill(item);
   369        await newTodo.press("Enter");
   370      }
   371  
   372      const todoItems = page.getByTestId("todo-item");
   373      const firstTodoCheck = todoItems.nth(0).getByRole("checkbox");
   374      await firstTodoCheck.check();
   375      await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
   376      await expect(firstTodoCheck).toBeChecked();
   377      await expect(todoItems).toHaveClass(["completed", ""]);
   378  
   379      // Ensure there is 1 completed item.
   380      await checkNumberOfCompletedTodosInLocalStorage(page, 1);
   381  
   382      // Now reload.
   383      await page.reload();
   384      await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
   385      await expect(firstTodoCheck).toBeChecked();
   386      await expect(todoItems).toHaveClass(["completed", ""]);
   387    });
   388  });
   389  
   390  test.describe("Routing", () => {
   391    test.beforeEach(async ({ page }) => {
   392      await createDefaultTodos(page);
   393      // make sure the app had a chance to save updated todos in storage
   394      // before navigating to a new view, otherwise the items can get lost :(
   395      // in some frameworks like Durandal
   396      await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
   397    });
   398  
   399    test("should allow me to display active items", async ({ page }) => {
   400      const todoItem = page.getByTestId("todo-item");
   401      await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
   402  
   403      await checkNumberOfCompletedTodosInLocalStorage(page, 1);
   404      await page.getByRole("link", { name: "Active" }).click();
   405      await expect(todoItem).toHaveCount(2);
   406      await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
   407    });
   408  
   409    test("should respect the back button", async ({ page }) => {
   410      const todoItem = page.getByTestId("todo-item");
   411      await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
   412  
   413      await checkNumberOfCompletedTodosInLocalStorage(page, 1);
   414  
   415      await test.step("Showing all items", async () => {
   416        await page.getByRole("link", { name: "All" }).click();
   417        await expect(todoItem).toHaveCount(3);
   418      });
   419  
   420      await test.step("Showing active items", async () => {
   421        await page.getByRole("link", { name: "Active" }).click();
   422      });
   423  
   424      await test.step("Showing completed items", async () => {
   425        await page.getByRole("link", { name: "Completed" }).click();
   426      });
   427  
   428      await expect(todoItem).toHaveCount(1);
   429      await page.goBack();
   430      await expect(todoItem).toHaveCount(2);
   431      await page.goBack();
   432      await expect(todoItem).toHaveCount(3);
   433    });
   434  
   435    test("should allow me to display completed items", async ({ page }) => {
   436      await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
   437      await checkNumberOfCompletedTodosInLocalStorage(page, 1);
   438      await page.getByRole("link", { name: "Completed" }).click();
   439      await expect(page.getByTestId("todo-item")).toHaveCount(1);
   440    });
   441  
   442    test("should allow me to display all items", async ({ page }) => {
   443      await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
   444      await checkNumberOfCompletedTodosInLocalStorage(page, 1);
   445      await page.getByRole("link", { name: "Active" }).click();
   446      await page.getByRole("link", { name: "Completed" }).click();
   447      await page.getByRole("link", { name: "All" }).click();
   448      await expect(page.getByTestId("todo-item")).toHaveCount(3);
   449    });
   450  
   451    test("should highlight the currently applied filter", async ({ page }) => {
   452      await expect(page.getByRole("link", { name: "All" })).toHaveClass(
   453        "selected",
   454      );
   455  
   456      //create locators for active and completed links
   457      const activeLink = page.getByRole("link", { name: "Active" });
   458      const completedLink = page.getByRole("link", { name: "Completed" });
   459      await activeLink.click();
   460  
   461      // Page change - active items.
   462      await expect(activeLink).toHaveClass("selected");
   463      await completedLink.click();
   464  
   465      // Page change - completed items.
   466      await expect(completedLink).toHaveClass("selected");
   467    });
   468  });
   469  
   470  async function createDefaultTodos(page: Page) {
   471    // create a new todo locator
   472    const newTodo = page.getByPlaceholder("What needs to be done?");
   473  
   474    for (const item of TODO_ITEMS) {
   475      await newTodo.fill(item);
   476      await newTodo.press("Enter");
   477    }
   478  }
   479  
   480  async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
   481    return await page.waitForFunction((e) => {
   482      return JSON.parse(localStorage["react-todos"]).length === e;
   483    }, expected);
   484  }
   485  
   486  async function checkNumberOfCompletedTodosInLocalStorage(
   487    page: Page,
   488    expected: number,
   489  ) {
   490    return await page.waitForFunction((e) => {
   491      return (
   492        JSON.parse(localStorage["react-todos"]).filter(
   493          (todo: any) => todo.completed,
   494        ).length === e
   495      );
   496    }, expected);
   497  }
   498  
   499  async function checkTodosInLocalStorage(page: Page, title: string) {
   500    return await page.waitForFunction((t) => {
   501      return JSON.parse(localStorage["react-todos"])
   502        .map((todo: any) => todo.title)
   503        .includes(t);
   504    }, title);
   505  }