chore: setup playwright ui & headless

This commit is contained in:
Bart Stefanski 2023-04-21 11:27:42 +02:00
parent a862cdbe11
commit 09f71f6bcd
8 changed files with 597 additions and 23 deletions

27
.github/workflows/playwright.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run Playwright tests
run: yarn playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

5
.gitignore vendored
View file

@ -27,4 +27,7 @@ yarn-error.log*
.env.local .env.local
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
/test-results/
/playwright-report/
/playwright/.cache/

7
e2e/example.spec.ts Normal file
View file

@ -0,0 +1,7 @@
import { expect, test } from "@playwright/test"
test("has title", async ({ page }) => {
await page.goto("./")
await expect(page).toHaveTitle(/Hello world/)
})

View file

@ -11,15 +11,12 @@
"storybook": "FORCE_COLOR=1 storybook dev -p 6006", "storybook": "FORCE_COLOR=1 storybook dev -p 6006",
"build-storybook": "FORCE_COLOR=1 storybook build", "build-storybook": "FORCE_COLOR=1 storybook build",
"test": "FORCE_COLOR=1 jest --passWithNoTests", "test": "FORCE_COLOR=1 jest --passWithNoTests",
"e2e:headless": "playwright test",
"e2e:ui": "playwright test --ui",
"format": "prettier --write \"**/*.{ts,tsx,md}\"", "format": "prettier --write \"**/*.{ts,tsx,md}\"",
"postinstall": "patch-package" "postinstall": "patch-package"
}, },
"dependencies": { "dependencies": {
"next": "^13.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"lodash": "^4.17.21",
"tailwind-merge": "^1.10.0",
"@radix-ui/react-accordion": "^1.1.1", "@radix-ui/react-accordion": "^1.1.1",
"@radix-ui/react-checkbox": "^1.0.3", "@radix-ui/react-checkbox": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.3", "@radix-ui/react-dialog": "^1.0.3",
@ -35,11 +32,18 @@
"@radix-ui/react-tabs": "^1.0.3", "@radix-ui/react-tabs": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.5", "@radix-ui/react-tooltip": "^1.0.5",
"class-variance-authority": "^0.4.0" "class-variance-authority": "^0.4.0",
"lodash": "^4.17.21",
"next": "^13.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.0.0", "@babel/core": "^7.0.0",
"@jest/globals": "^29.5.0",
"@next/bundle-analyzer": "^13.3.0", "@next/bundle-analyzer": "^13.3.0",
"@playwright/test": "^1.32.3",
"@storybook/addon-essentials": "^7.0.5", "@storybook/addon-essentials": "^7.0.5",
"@storybook/addon-interactions": "^7.0.5", "@storybook/addon-interactions": "^7.0.5",
"@storybook/addon-links": "^7.0.5", "@storybook/addon-links": "^7.0.5",
@ -47,33 +51,32 @@
"@storybook/nextjs": "^7.0.5", "@storybook/nextjs": "^7.0.5",
"@storybook/react": "^7.0.5", "@storybook/react": "^7.0.5",
"@storybook/testing-library": "^0.0.14-next.2", "@storybook/testing-library": "^0.0.14-next.2",
"@types/node": "^17.0.12",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"eslint": "7.32.0",
"next-compose-plugins": "^2.2.1",
"storybook": "^7.0.5",
"eslint-config-next": "13.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "7.31.8",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.54.1",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-storybook": "^0.6.11",
"@jest/globals": "^29.5.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@total-typescript/ts-reset": "^0.4.2", "@total-typescript/ts-reset": "^0.4.2",
"@types/node": "^17.0.12",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.54.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"eslint": "7.32.0",
"eslint-config-next": "13.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-react": "7.31.8",
"eslint-plugin-storybook": "^0.6.11",
"fetch-mock": "^9.11.0", "fetch-mock": "^9.11.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0", "jest-environment-jsdom": "^29.5.0",
"next-compose-plugins": "^2.2.1",
"patch-package": "^6.5.1", "patch-package": "^6.5.1",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "latest", "prettier": "latest",
"prettier-plugin-tailwindcss": "^0.2.7", "prettier-plugin-tailwindcss": "^0.2.7",
"storybook": "^7.0.5",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"tsc": "^2.0.4", "tsc": "^2.0.4",

View file

@ -1,6 +1,11 @@
import Head from "next/head"
export default function Web() { export default function Web() {
return ( return (
<> <>
<Head>
<title>Hello world</title>
</Head>
<section className="bg-white dark:bg-gray-900"> <section className="bg-white dark:bg-gray-900">
<div className="mx-auto grid max-w-screen-xl px-4 py-8 text-center lg:py-16"> <div className="mx-auto grid max-w-screen-xl px-4 py-8 text-center lg:py-16">
<div className="mx-auto place-self-center"> <div className="mx-auto place-self-center">

77
playwright.config.ts Normal file
View file

@ -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: "./e2e",
/* 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 dev",
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
},
})

View file

@ -0,0 +1,437 @@
import { test, expect, type Page } from '@playwright/test';
test.beforeEach(async ({ page }) => {
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);
}

View file

@ -1807,6 +1807,16 @@
"@nodelib/fs.scandir" "2.1.5" "@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0" fastq "^1.6.0"
"@playwright/test@^1.32.3":
version "1.32.3"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.32.3.tgz#75be8346d4ef289896835e1d2a86fdbe3d9be92a"
integrity sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==
dependencies:
"@types/node" "*"
playwright-core "1.32.3"
optionalDependencies:
fsevents "2.3.2"
"@pmmmwh/react-refresh-webpack-plugin@^0.5.5": "@pmmmwh/react-refresh-webpack-plugin@^0.5.5":
version "0.5.10" version "0.5.10"
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8"
@ -6524,7 +6534,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.3.2, fsevents@~2.3.2: fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@ -8945,6 +8955,11 @@ pkg-types@^1.0.2:
mlly "^1.1.1" mlly "^1.1.1"
pathe "^1.1.0" pathe "^1.1.0"
playwright-core@1.32.3:
version "1.32.3"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.32.3.tgz#e6dc7db0b49e9b6c0b8073c4a2d789a96f519c48"
integrity sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==
pnp-webpack-plugin@^1.7.0: pnp-webpack-plugin@^1.7.0:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9"