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 }