From ab9e5a6707b822393a04287eea80379c5993892d Mon Sep 17 00:00:00 2001 From: FranGuam <53105842+FranGuam@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:32:00 +0800 Subject: [PATCH] feat: playwright tests (#1626) * feat: playwright tests * feat(test): demo tests * feat(test): prettier ci & cd * fix: lower z-index for siders * fix(ci): install browser --- .github/workflows/actions.yml | 68 ----- .github/workflows/cd.yml | 25 ++ .github/workflows/ci.yml | 60 ++++ .gitignore | 4 + package.json | 5 +- playwright.config.ts | 77 +++++ src/__tests__/App.test.tsx | 19 -- src/app/ContestSite/MenuPage.tsx | 2 +- src/app/InfoSite/index.tsx | 2 +- tests/demo-todo-app.spec.ts | 489 +++++++++++++++++++++++++++++++ tests/example.spec.ts | 20 ++ yarn.lock | 267 +++-------------- 12 files changed, 714 insertions(+), 324 deletions(-) delete mode 100644 .github/workflows/actions.yml create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 playwright.config.ts delete mode 100644 src/__tests__/App.test.tsx create mode 100644 tests/demo-todo-app.spec.ts create mode 100644 tests/example.spec.ts diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml deleted file mode 100644 index 6420d4788..000000000 --- a/.github/workflows/actions.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: actions - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "20.x" - - - name: Get yarn cache path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - - - name: Restore yarn cache - uses: actions/cache@v4 - id: yarn-cache - with: - path: ${{steps.yarn-cache-dir-path.outputs.dir}} - key: ${{runner.os}}-yarn-${{hashFiles('**/yarn.lock')}} - restore-keys: | - ${{runner.os}}-yarn- - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Check grammar - run: | - yarn lint - yarn typecheck - - - name: Test run - run: | - yarn test - - - name: Test build - run: | - yarn build - - deploy: - if: | - github.event_name == 'push' && - github.ref_name == 'master' && - github.repository_owner == 'eesast' - needs: test - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Login to DockerHub - run: echo "${{secrets.DOCKER_PASSWORD}}" | docker login -u "${{secrets.DOCKER_USERNAME}}" --password-stdin - - - name: Build docker image - shell: bash - run: docker build -t eesast/web:latest . - - - name: Push docker image - shell: bash - run: docker push eesast/web:latest diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 000000000..4ec745829 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,25 @@ +name: CD + +on: + push: + branches: [ master ] + +jobs: + deploy: + if: github.repository_owner == 'eesast' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to DockerHub + run: echo "${{secrets.DOCKER_PASSWORD}}" | docker login -u "${{secrets.DOCKER_USERNAME}}" --password-stdin + + - name: Build docker image + shell: bash + run: docker build -t eesast/web:latest . + + - name: Push docker image + shell: bash + run: docker push eesast/web:latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..c35b3feb7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + branches: + - '*' + - '!master' + pull_request: + branches: [ master ] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - name: Get yarn cache path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - name: Restore yarn cache + uses: actions/cache@v4 + id: yarn-cache + with: + path: ${{steps.yarn-cache-dir-path.outputs.dir}} + key: ${{runner.os}}-yarn-${{hashFiles('**/yarn.lock')}} + restore-keys: | + ${{runner.os}}-yarn- + + - name: Install dependencies + run: | + yarn install --frozen-lockfile + yarn playwright install --with-deps + + - name: Check grammar + run: | + yarn lint + yarn typecheck + + - name: Run Playwright tests + run: yarn playwright test + + - name: Upload Playwright report + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 514521086..e121a32ef 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ yarn-error.log* !.vscode/settings.json .idea/* .eslintcache +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package.json b/package.json index e62d590ef..f62e363d3 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,9 @@ "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/typescript-operations": "4.2.0", "@graphql-codegen/typescript-react-apollo": "4.3.0", - "@testing-library/react": "14.2.1", + "@playwright/test": "^1.42.1", "@types/file-saver": "2.0.7", "@types/is-url": "1.2.32", - "@types/jest": "29.5.12", "@types/lodash.get": "4.4.9", "@types/node": "20.11.24", "@types/pizzip": "3.0.5", @@ -79,7 +78,7 @@ "reset": "rimraf node_modules && rimraf yarn.lock && yarn", "clean": "rimraf ./node_modules/.cache && rimraf build && rimraf electron", "start": "react-app-rewired start", - "test": "react-app-rewired test --transformIgnorePatterns \"node_modules/(?!antd|rc|axios)\"", + "test": "playwright test --ui", "build": "react-app-rewired build", "analyze": "cross-env ANALYZER=true yarn build", "lint": "eslint src --ext .js,.jsx,.ts,.tsx", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..594858da9 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: "Mobile Chrome", + // use: { ...devices["Pixel 5"] }, + // }, + // { + // name: "Mobile Safari", + // use: { ...devices["iPhone 12"] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "yarn start", + url: "http://localhost:3000/?source=test", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx deleted file mode 100644 index 4dfa47296..000000000 --- a/src/__tests__/App.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; -import { BrowserRouter } from "react-router-dom"; -import { ApolloProvider } from "@apollo/client"; -import "../index.css"; -import App from "../app"; -import { client } from "../api/apollo"; - -test("renders without crashing", () => { - render( - - - - - - - , - ); -}); diff --git a/src/app/ContestSite/MenuPage.tsx b/src/app/ContestSite/MenuPage.tsx index 9c7c3302e..c38ded251 100644 --- a/src/app/ContestSite/MenuPage.tsx +++ b/src/app/ContestSite/MenuPage.tsx @@ -261,7 +261,7 @@ const MenuPage: React.FC = (props) => { left: 0, top: "72px", bottom: 0, - zIndex: 99, + zIndex: 98, }} > = ({ mode, user }) => { left: 0, top: "72px", bottom: 0, - zIndex: 99, + zIndex: 98, }} > { + await page.goto("https://demo.playwright.dev/todomvc"); +}); + +const TODO_ITEMS = [ + "buy some cheese", + "feed the cat", + "book a doctors appointment", +]; + +test.describe("New Todo", () => { + test("should allow me to add todo items", async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press("Enter"); + + // Make sure the list only has one todo item. + await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press("Enter"); + + // Make sure the list now has two todo items. + await expect(page.getByTestId("todo-title")).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1], + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test("should clear text input field when an item is added", async ({ + page, + }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press("Enter"); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test("should append new items to the bottom of the list", async ({ + page, + }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId("todo-count"); + + // Check test using different methods. + await expect(page.getByText("3 items left")).toBeVisible(); + await expect(todoCount).toHaveText("3 items left"); + await expect(todoCount).toContainText("3"); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe("Mark all as completed", () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test("should allow me to mark all items as completed", async ({ page }) => { + // Complete all todos. + await page.getByLabel("Mark all as complete").check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId("todo-item")).toHaveClass([ + "completed", + "completed", + "completed", + ]); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test("should allow me to clear the complete state of all items", async ({ + page, + }) => { + const toggleAll = page.getByLabel("Mark all as complete"); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]); + }); + + test("complete all checkbox should update state when items are completed / cleared", async ({ + page, + }) => { + const toggleAll = page.getByLabel("Mark all as complete"); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId("todo-item").nth(0); + await firstTodo.getByRole("checkbox").uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole("checkbox").check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe("Item", () => { + test("should allow me to mark items as complete", async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press("Enter"); + } + + // Check first item. + const firstTodo = page.getByTestId("todo-item").nth(0); + await firstTodo.getByRole("checkbox").check(); + await expect(firstTodo).toHaveClass("completed"); + + // Check second item. + const secondTodo = page.getByTestId("todo-item").nth(1); + await expect(secondTodo).not.toHaveClass("completed"); + await secondTodo.getByRole("checkbox").check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass("completed"); + await expect(secondTodo).toHaveClass("completed"); + }); + + test("should allow me to un-mark items as complete", async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press("Enter"); + } + + const firstTodo = page.getByTestId("todo-item").nth(0); + const secondTodo = page.getByTestId("todo-item").nth(1); + const firstTodoCheckbox = firstTodo.getByRole("checkbox"); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass("completed"); + await expect(secondTodo).not.toHaveClass("completed"); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass("completed"); + await expect(secondTodo).not.toHaveClass("completed"); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test("should allow me to edit an item", async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId("todo-item"); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue( + TODO_ITEMS[1], + ); + await secondTodo + .getByRole("textbox", { name: "Edit" }) + .fill("buy some sausages"); + await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter"); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + "buy some sausages", + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, "buy some sausages"); + }); +}); + +test.describe("Editing", () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test("should hide other controls when editing", async ({ page }) => { + const todoItem = page.getByTestId("todo-item").nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole("checkbox")).not.toBeVisible(); + await expect( + todoItem.locator("label", { + hasText: TODO_ITEMS[1], + }), + ).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test("should save edits on blur", async ({ page }) => { + const todoItems = page.getByTestId("todo-item"); + await todoItems.nth(1).dblclick(); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .fill("buy some sausages"); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .dispatchEvent("blur"); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + "buy some sausages", + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, "buy some sausages"); + }); + + test("should trim entered text", async ({ page }) => { + const todoItems = page.getByTestId("todo-item"); + await todoItems.nth(1).dblclick(); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .fill(" buy some sausages "); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .press("Enter"); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + "buy some sausages", + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, "buy some sausages"); + }); + + test("should remove the item if an empty text string was entered", async ({ + page, + }) => { + const todoItems = page.getByTestId("todo-item"); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill(""); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .press("Enter"); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test("should cancel edits on escape", async ({ page }) => { + const todoItems = page.getByTestId("todo-item"); + await todoItems.nth(1).dblclick(); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .fill("buy some sausages"); + await todoItems + .nth(1) + .getByRole("textbox", { name: "Edit" }) + .press("Escape"); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe("Counter", () => { + test("should display the current number of todo items", async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + // create a todo count locator + const todoCount = page.getByTestId("todo-count"); + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press("Enter"); + + await expect(todoCount).toContainText("1"); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press("Enter"); + await expect(todoCount).toContainText("2"); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe("Clear completed button", () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test("should display the correct text", async ({ page }) => { + await page.locator(".todo-list li .toggle").first().check(); + await expect( + page.getByRole("button", { name: "Clear completed" }), + ).toBeVisible(); + }); + + test("should remove completed items when clicked", async ({ page }) => { + const todoItems = page.getByTestId("todo-item"); + await todoItems.nth(1).getByRole("checkbox").check(); + await page.getByRole("button", { name: "Clear completed" }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test("should be hidden when there are no items that are completed", async ({ + page, + }) => { + await page.locator(".todo-list li .toggle").first().check(); + await page.getByRole("button", { name: "Clear completed" }).click(); + await expect( + page.getByRole("button", { name: "Clear completed" }), + ).toBeHidden(); + }); +}); + +test.describe("Persistence", () => { + test("should persist its data", async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press("Enter"); + } + + const todoItems = page.getByTestId("todo-item"); + const firstTodoCheck = todoItems.nth(0).getByRole("checkbox"); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(["completed", ""]); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(["completed", ""]); + }); +}); + +test.describe("Routing", () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test("should allow me to display active items", async ({ page }) => { + const todoItem = page.getByTestId("todo-item"); + await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole("link", { name: "Active" }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test("should respect the back button", async ({ page }) => { + const todoItem = page.getByTestId("todo-item"); + await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step("Showing all items", async () => { + await page.getByRole("link", { name: "All" }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step("Showing active items", async () => { + await page.getByRole("link", { name: "Active" }).click(); + }); + + await test.step("Showing completed items", async () => { + await page.getByRole("link", { name: "Completed" }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test("should allow me to display completed items", async ({ page }) => { + await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole("link", { name: "Completed" }).click(); + await expect(page.getByTestId("todo-item")).toHaveCount(1); + }); + + test("should allow me to display all items", async ({ page }) => { + await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole("link", { name: "Active" }).click(); + await page.getByRole("link", { name: "Completed" }).click(); + await page.getByRole("link", { name: "All" }).click(); + await expect(page.getByTestId("todo-item")).toHaveCount(3); + }); + + test("should highlight the currently applied filter", async ({ page }) => { + await expect(page.getByRole("link", { name: "All" })).toHaveClass( + "selected", + ); + + //create locators for active and completed links + const activeLink = page.getByRole("link", { name: "Active" }); + const completedLink = page.getByRole("link", { name: "Completed" }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass("selected"); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass("selected"); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder("What needs to be done?"); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press("Enter"); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction((e) => { + return JSON.parse(localStorage["react-todos"]).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage( + page: Page, + expected: number, +) { + return await page.waitForFunction((e) => { + return ( + JSON.parse(localStorage["react-todos"]).filter( + (todo: any) => todo.completed, + ).length === e + ); + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction((t) => { + return JSON.parse(localStorage["react-todos"]) + .map((todo: any) => todo.title) + .includes(t); + }, title); +} diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 000000000..b60fe7cd0 --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from "@playwright/test"; + +test("has title", async ({ page }) => { + await page.goto("https://playwright.dev/"); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test("get started link", async ({ page }) => { + await page.goto("https://playwright.dev/"); + + // Click the get started link. + await page.getByRole("link", { name: "Get started" }).click(); + + // Expects page to have a heading with the name of Installation. + await expect( + page.getByRole("heading", { name: "Installation" }), + ).toBeVisible(); +}); diff --git a/yarn.lock b/yarn.lock index bc5902f29..4b5fdaf55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2300,13 +2300,6 @@ "@types/node" "*" jest-mock "^27.5.1" -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -2366,13 +2359,6 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -2456,18 +2442,6 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.4" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz#9b18145d26cf33d08576cf4c7665b28554480ed7" @@ -2578,6 +2552,13 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@playwright/test@^1.42.1": + version "1.42.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.42.1.tgz#9eff7417bcaa770e9e9a00439e078284b301f31c" + integrity sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ== + dependencies: + playwright "1.42.1" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.3": version "0.5.11" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz#7c2268cedaa0644d677e8c4f377bc8fb304f714a" @@ -2721,11 +2702,6 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - "@sinonjs/commons@^1.7.0": version "1.8.6" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" @@ -2853,29 +2829,6 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" -"@testing-library/dom@^9.0.0": - version "9.3.4" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" - integrity sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^5.0.1" - aria-query "5.1.3" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.5.0" - pretty-format "^27.0.2" - -"@testing-library/react@14.2.1": - version "14.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.2.1.tgz#bf69aa3f71c36133349976a4a2da3687561d8310" - integrity sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^9.0.0" - "@types/react-dom" "^18.0.0" - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2886,11 +2839,6 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/aria-query@^5.0.1": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" - integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== - "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -3087,14 +3035,6 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@29.5.12": - version "29.5.12" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" - integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - "@types/js-yaml@^4.0.0": version "4.0.9" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" @@ -3207,7 +3147,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@18.2.19", "@types/react-dom@^18.0.0": +"@types/react-dom@18.2.19": version "18.2.19" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.19.tgz#b84b7c30c635a6c26c6a6dfbb599b2da9788be58" integrity sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA== @@ -3960,13 +3900,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" - integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== - dependencies: - deep-equal "^2.0.5" - aria-query@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" @@ -3974,7 +3907,7 @@ aria-query@^5.3.0: dependencies: dequal "^2.0.3" -array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: +array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== @@ -5446,30 +5379,6 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== -deep-equal@^2.0.5: - version "2.2.3" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" - integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.5" - es-get-iterator "^1.1.3" - get-intrinsic "^1.2.2" - is-arguments "^1.1.1" - is-array-buffer "^3.0.2" - is-date-object "^1.0.5" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - isarray "^2.0.5" - object-is "^1.1.5" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.1" - side-channel "^1.0.4" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.13" - deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -5587,11 +5496,6 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5632,11 +5536,6 @@ docxtemplater@3.46.0: dependencies: "@xmldom/xmldom" "^0.8.10" -dom-accessibility-api@^0.5.9: - version "0.5.16" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" - integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== - dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -5892,21 +5791,6 @@ es-errors@^1.0.0, es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-get-iterator@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" - integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - has-symbols "^1.0.3" - is-arguments "^1.1.1" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.7" - isarray "^2.0.5" - stop-iteration-iterator "^1.0.0" - es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: version "1.0.17" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz#123d1315780df15b34eb181022da43e734388bb8" @@ -6340,17 +6224,6 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" -expect@^29.0.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - express@^4.17.3: version "4.18.3" resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" @@ -6702,6 +6575,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -7352,7 +7230,7 @@ inquirer@^8.0.0: through "^2.3.6" wrap-ansi "^6.0.1" -internal-slot@^1.0.4, internal-slot@^1.0.5, internal-slot@^1.0.7: +internal-slot@^1.0.5, internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== @@ -7399,15 +7277,7 @@ is-alphanumerical@^2.0.0: is-alphabetical "^2.0.0" is-decimal "^2.0.0" -is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: +is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== @@ -7543,7 +7413,7 @@ is-lower-case@^2.0.2: dependencies: tslib "^2.0.3" -is-map@^2.0.1, is-map@^2.0.2: +is-map@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== @@ -7625,7 +7495,7 @@ is-root@^2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-set@^2.0.1, is-set@^2.0.2: +is-set@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== @@ -7913,16 +7783,6 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -7971,11 +7831,6 @@ jest-get-type@^27.5.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -8037,16 +7892,6 @@ jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" @@ -8077,21 +7922,6 @@ jest-message-util@^28.1.3: slash "^3.0.0" stack-utils "^2.0.3" -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -8255,18 +8085,6 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" @@ -8823,11 +8641,6 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== -lz-string@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" - integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== - magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" @@ -9479,14 +9292,6 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== -object-is@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" - integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -9925,6 +9730,20 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +playwright-core@1.42.1: + version "1.42.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.42.1.tgz#13c150b93c940a3280ab1d3fbc945bc855c9459e" + integrity sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA== + +playwright@1.42.1: + version "1.42.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.42.1.tgz#79c828b51fe3830211137550542426111dc8239f" + integrity sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg== + dependencies: + playwright-core "1.42.1" + optionalDependencies: + fsevents "2.3.2" + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -10524,7 +10343,7 @@ pretty-error@^4.0.0: lodash "^4.17.20" renderkid "^3.0.0" -pretty-format@^27.0.2, pretty-format@^27.5.1: +pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -10543,15 +10362,6 @@ pretty-format@^28.1.3: ansi-styles "^5.0.0" react-is "^18.0.0" -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - pretty-time@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" @@ -11367,7 +11177,7 @@ regex-parser@^2.2.11: resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.3.0.tgz#4bb61461b1a19b8b913f3960364bb57887f920ee" integrity sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg== -regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: +regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== @@ -12143,13 +11953,6 @@ std-env@^3.7.0: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== -stop-iteration-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" - integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== - dependencies: - internal-slot "^1.0.4" - streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" @@ -13485,7 +13288,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.9: +which-typed-array@^1.1.14, which-typed-array@^1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.14.tgz#1f78a111aee1e131ca66164d8bdc3ab062c95a06" integrity sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==