refactor: Separate storybook from design-system (no-changelog) (#23983)

This commit is contained in:
Alex Grozav 2026-01-09 13:48:20 +02:00 committed by GitHub
parent 77b8c1b317
commit ef8daaf023
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1498 additions and 1112 deletions

View file

@ -23,6 +23,8 @@ jobs:
filters: |
design_system:
- 'packages/frontend/@n8n/design-system/**'
- 'packages/frontend/@n8n/chat/**'
- 'packages/frontend/@n8n/storybook/**'
- '.github/workflows/test-visual-storybook.yml'
outputs:
has_changes: ${{ steps.changed.outputs.design_system || 'false' }}
@ -48,14 +50,14 @@ jobs:
- name: Setup Node.js
uses: ./.github/actions/setup-nodejs
with:
build-command: pnpm run build --filter=@n8n/utils --filter=@n8n/vitest-config --filter=@n8n/design-system
build-command: pnpm run build --filter=@n8n/utils --filter=@n8n/vitest-config --filter=@n8n/chat --filter=@n8n/design-system --filter=@n8n/storybook
- name: Publish to Chromatic
uses: chromaui/action@1cfa065cbdab28f6ca3afaeb3d761383076a35aa # v11
id: chromatic_tests
continue-on-error: true
with:
workingDir: packages/frontend/@n8n/design-system
workingDir: packages/frontend/@n8n/storybook
autoAcceptChanges: 'master'
skip: 'release/**'
onlyChanged: true

View file

@ -8,6 +8,8 @@ on:
branches:
- master
paths:
- 'packages/frontend/@n8n/storybook/**'
- 'packages/frontend/@n8n/chat/**'
- 'packages/frontend/@n8n/design-system/**'
- '.github/workflows/test-visual-storybook.yml'
concurrency:
@ -33,9 +35,9 @@ jobs:
build-command: pnpm run build --filter=@n8n/utils --filter=@n8n/vitest-config --filter=@n8n/design-system
- name: Build
working-directory: packages/frontend/@n8n/design-system
working-directory: packages/frontend/@n8n/storybook
run: |
pnpm build:storybook
pnpm build
- name: Setup Wrangler Pre-requisites
run: |
@ -48,5 +50,5 @@ jobs:
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy packages/frontend/@n8n/design-system/storybook-static --project-name=storybook --branch=${{github.ref_name}} --commit-hash=${{ github.sha }}
command: pages deploy packages/frontend/@n8n/storybook/storybook-static --project-name=storybook --branch=${{github.ref_name}} --commit-hash=${{ github.sha }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

View file

@ -53,12 +53,12 @@
"worker": "./packages/cli/bin/n8n worker"
},
"devDependencies": {
"@babel/preset-env": "^7.26.0",
"@biomejs/biome": "^1.9.0",
"@n8n/eslint-config": "workspace:*",
"@types/jest": "^29.5.3",
"@types/node": "*",
"@types/supertest": "^6.0.3",
"@babel/preset-env": "^7.26.0",
"babel-plugin-transform-import-meta": "^2.3.2",
"cross-env": "^7.0.3",
"eslint": "catalog:",
@ -143,7 +143,8 @@
"minifaker": "patches/minifaker.patch",
"z-vue-scan": "patches/z-vue-scan.patch",
"@lezer/highlight": "patches/@lezer__highlight.patch",
"v-code-diff": "patches/v-code-diff.patch"
"v-code-diff": "patches/v-code-diff.patch",
"assert@2.1.0": "patches/assert@2.1.0.patch"
}
}
}

View file

@ -1,4 +0,0 @@
import { sharedConfig } from '@n8n/storybook/main';
const config = { ...sharedConfig };
export default config;

View file

@ -1,7 +0,0 @@
html,
body,
#storybook-root,
#n8n-chat {
width: 100%;
height: 100%;
}

View file

@ -1,15 +0,0 @@
import type { Preview } from '@storybook/vue3';
import './preview.scss';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;

View file

@ -2,7 +2,7 @@
"name": "@n8n/chat",
"version": "1.3.0",
"scripts": {
"dev": "pnpm run storybook",
"dev": "pnpm run --dir=../storybook dev --initial-path=/docs/chat-chat--docs",
"build": "pnpm build:vite && pnpm build:bundle",
"build:vite": "cross-env vite build",
"build:bundle": "cross-env INCLUDE_VUE=true vite build",
@ -14,10 +14,8 @@
"lint:fix": "eslint src --fix",
"lint:styles": "stylelint \"src/**/*.{scss,sass,vue}\" --cache",
"lint:styles:fix": "stylelint \"src/**/*.{scss,sass,vue}\" --fix --cache",
"format": "biome format --write src .storybook && prettier --write src/ --ignore-path ../../../../.prettierignore",
"format:check": "biome ci src .storybook && prettier --check src/ --ignore-path ../../../../.prettierignore",
"storybook": "storybook dev -p 6006 --no-open",
"build:storybook": "storybook build"
"format": "biome format --write src && prettier --write src/ --ignore-path ../../../../.prettierignore",
"format:check": "biome ci src && prettier --check src/ --ignore-path ../../../../.prettierignore"
},
"types": "./dist/index.d.ts",
"main": "./dist/chat.umd.js",
@ -48,11 +46,11 @@
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.54",
"@n8n/storybook": "workspace:*",
"@n8n/eslint-config": "workspace:*",
"@n8n/stylelint-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@n8n/vitest-config": "workspace:*",
"@storybook/vue3-vite": "catalog:storybook",
"@vitejs/plugin-vue": "catalog:frontend",
"@vitest/coverage-v8": "catalog:",
"unplugin-icons": "^0.19.0",

View file

@ -7,7 +7,7 @@ import type { ChatOptions } from '@n8n/chat/types';
const webhookUrl = 'http://localhost:5678/webhook/ad712f8b-3546-4d08-b049-e0d035334a4c/chat';
const meta = {
title: 'Chat',
title: 'Chat/Chat',
render: (args: Partial<ChatOptions>) => ({
setup() {
onMounted(() => {

View file

@ -1,4 +0,0 @@
import { sharedConfig } from '@n8n/storybook/main';
const config = { ...sharedConfig, staticDirs: ['../assets'] };
export default config;

View file

@ -5,15 +5,13 @@
"main": "src/index.ts",
"import": "src/index.ts",
"scripts": {
"dev": "pnpm run storybook",
"dev": "pnpm run --dir=../storybook dev",
"clean": "rimraf dist .turbo",
"build": "vite build",
"typecheck": "vue-tsc --noEmit",
"typecheck:watch": "vue-tsc --watch --noEmit",
"test": "vitest run",
"test:dev": "vitest",
"build:storybook": "storybook build",
"storybook": "storybook dev -p 6006 --no-open",
"chromatic": "chromatic",
"format": "biome format --write . && prettier --write . --ignore-path ../../../../.prettierignore",
"format:check": "biome ci . && prettier --check . --ignore-path ../../../../.prettierignore",
@ -24,10 +22,10 @@
},
"devDependencies": {
"@n8n/eslint-config": "workspace:*",
"@n8n/storybook": "workspace:*",
"@n8n/stylelint-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@n8n/vitest-config": "workspace:*",
"@storybook/vue3-vite": "catalog:storybook",
"@testing-library/jest-dom": "catalog:frontend",
"@testing-library/user-event": "catalog:frontend",
"@testing-library/vue": "catalog:frontend",
@ -38,6 +36,7 @@
"@types/sanitize-html": "^2.11.0",
"@vitejs/plugin-vue": "catalog:frontend",
"@vitest/coverage-v8": "catalog:",
"@vue/test-utils": "catalog:frontend",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"sass": "^1.71.1",
@ -46,8 +45,7 @@
"vite": "catalog:",
"vitest": "catalog:",
"vitest-mock-extended": "catalog:",
"vue-tsc": "catalog:frontend",
"@vue/test-utils": "catalog:frontend"
"vue-tsc": "catalog:frontend"
},
"dependencies": {
"@internationalized/date": "^3.9.0",

View file

@ -4,7 +4,7 @@ import DateRangePicker from './DateRangePicker.vue';
const meta = {
component: DateRangePicker,
title: 'DateRangePicker',
title: 'Components v2/DateRangePicker',
} satisfies Meta<typeof DateRangePicker>;
export default meta;

View file

@ -43,7 +43,7 @@ $color-danger-light-1: var(--color--danger);
$color-danger-light: var(--color--danger--tint-3);
$color-danger-lighter: var(--color--danger--tint-4);
$color-info-light-1: var(-color-info);
$color-info-light-1: var(--color--info);
$color-info-lighter: var(--color--info--tint-2);
// Background

View file

@ -4,6 +4,7 @@
"baseUrl": ".",
"rootDirs": [".", "../composables/src"],
"outDir": "dist",
"moduleResolution": "bundler",
"types": ["vite/client", "unplugin-icons/types/vue", "vitest/globals"],
"typeRoots": [
"./node_modules/@testing-library",
@ -17,5 +18,5 @@
"@n8n/utils*": ["../../../@n8n/utils/src*"]
}
},
"include": ["src/**/*.ts", "src/**/*.vue", ".storybook/**/*.ts"]
"include": ["src/**/*.ts", "src/**/*.vue"]
}

View file

@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*storybook.log
storybook-static

View file

@ -0,0 +1,31 @@
import type { StorybookConfig } from '@storybook/vue3-vite';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
}
const config: StorybookConfig = {
stories: [
'../../design-system/src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
'../../chat/src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
addons: [
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-vitest'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('@storybook/addon-docs'),
],
framework: getAbsolutePath('@storybook/vue3-vite'),
staticDirs: ['../../design-system/assets'],
core: {
disableTelemetry: true,
},
};
export default config;

View file

@ -1,11 +1,10 @@
import { sharedTags } from '@n8n/storybook/main';
import { withThemeByDataAttribute } from '@storybook/addon-themes';
import { setup } from '@storybook/vue3';
import ElementPlus from 'element-plus';
// @ts-expect-error no types
import lang from 'element-plus/dist/locale/en.mjs';
import { N8nPlugin } from '../src/plugin';
import { N8nPlugin } from '@n8n/design-system';
import './storybook.scss';
import { allModes } from './modes';
@ -94,4 +93,4 @@ export const decorators = [
}),
];
export const tags = sharedTags;
export const tags = ['autodocs'];

View file

@ -1,6 +1,6 @@
@use '../src/css/base.scss';
@use '../src/css/reset.scss';
@use '../src/css/index.scss';
@use '../../design-system/src/css/base.scss';
@use '../../design-system/src/css/reset.scss';
@use '../../design-system/src/css/index.scss';
.multi-container > * {
margin-bottom: 10px;

View file

@ -0,0 +1,3 @@
# @n8n/storybook
This package contains the Storybook setup for n8n components. Storybook is a tool for developing UI components in isolation, which helps in building, testing, and documenting components effectively.

View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>n8n - Storybook</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -1,22 +0,0 @@
import type { StorybookConfig } from '@storybook/vue3-vite';
export const sharedConfig: StorybookConfig = {
stories: ['../src/**/*.stories.ts'],
addons: [
'storybook-dark-mode',
'@storybook/addon-a11y',
'@storybook/addon-themes',
'@storybook/addon-links',
'@chromatic-com/storybook',
],
staticDirs: ['../public'],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
core: {
disableTelemetry: true,
},
};
export const sharedTags: string[] = ['autodocs'];

View file

@ -1,22 +1,44 @@
{
"name": "@n8n/storybook",
"private": true,
"version": "0.0.1",
"exports": {
"./main": "./main.ts"
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "storybook dev -p 6006",
"build": "storybook build",
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json"
},
"dependencies": {
"@n8n/chat": "workspace:*",
"@n8n/design-system": "workspace:*",
"vue": "catalog:frontend",
"vue-tsc": "catalog:frontend"
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.5",
"@storybook/addon-a11y": "9.1.7",
"@storybook/addon-actions": "9.0.8",
"@storybook/addon-docs": "9.1.7",
"@storybook/addon-links": "9.1.7",
"@storybook/addon-themes": "9.1.7",
"@storybook/vue3": "9.1.7",
"@storybook/vue3-vite": "9.1.7",
"chromatic": "^11.27.0",
"eslint-plugin-storybook": "9.1.7",
"storybook": "9.1.7",
"storybook-dark-mode": "^4.0.2"
"@chromatic-com/storybook": "catalog:storybook",
"@n8n/typescript-config": "workspace:*",
"@storybook/addon-a11y": "catalog:storybook",
"@storybook/addon-docs": "catalog:storybook",
"@storybook/addon-themes": "catalog:storybook",
"@storybook/addon-vitest": "catalog:storybook",
"@storybook/vue3-vite": "catalog:storybook",
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "catalog:frontend",
"@vitest/browser-playwright": "^4.0.16",
"@vitest/coverage-v8": "catalog:",
"@vue/tsconfig": "catalog:frontend",
"eslint-plugin-storybook": "catalog:storybook",
"playwright": "catalog:e2e",
"storybook": "catalog:storybook",
"typescript": "catalog:",
"unplugin-icons": "catalog:frontend",
"vite": "catalog:",
"vitest": "catalog:",
"vue-tsc": "catalog:frontend"
},
"eslintConfig": {
"extends": [
"plugin:storybook/recommended"
]
}
}
}

View file

@ -0,0 +1,28 @@
{
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"rootDirs": [".", "../composables/src"],
"outDir": "dist",
"moduleResolution": "bundler",
"types": [
"vite/client",
"vitest/globals",
"unplugin-icons/types/vue",
"../design-system/src/shims-modules.d.ts"
],
"paths": {
"@n8n/chat*": ["../chat/src*"],
"@n8n/design-system*": ["../design-system/src*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"../chat/src/**/*.stories.ts",
"../design-system/src/**/*.stories.ts"
]
}

View file

@ -0,0 +1,4 @@
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View file

@ -0,0 +1,35 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import icons from 'unplugin-icons/vite';
import path from 'path';
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
icons({
compiler: 'vue3',
autoInstall: true,
}),
],
resolve: {
alias: [
{
find: /^@n8n\/design-system$/,
replacement: path.resolve(__dirname, '../design-system/src/index.ts'),
},
{
find: /^@n8n\/design-system\/(.*)$/,
replacement: path.resolve(__dirname, '../design-system/src/$1'),
},
{
find: /^@n8n\/chat$/,
replacement: path.resolve(__dirname, '../chat/src/index.ts'),
},
{
find: /^@n8n\/chat\/(.*)$/,
replacement: path.resolve(__dirname, '../chat/src/$1'),
},
],
},
});

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ComponentInstance } from 'vue';
import type { ComponentPublicInstance } from 'vue';
import { computed, ref, onMounted, onUnmounted, watch } from 'vue';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import type { EventBus } from '@n8n/utils/event-bus';
@ -82,7 +82,7 @@ const projectStore = useProjectsStore();
const router = useRouter();
const i18n = useI18n();
const container = ref<HTMLDivElement>();
const dropdown = ref<ComponentInstance<typeof ResourceLocatorDropdown>>();
const dropdown = ref<ComponentPublicInstance<typeof ResourceLocatorDropdown>>();
const telemetry = useTelemetry();
const toast = useToast();

View file

@ -32,5 +32,5 @@
"useUnknownInCatchVariables": false
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
"exclude": ["src/plugins/codemirror/typescript/worker/**/*.d.ts"]
"exclude": ["src/**/*.stories.ts", "src/plugins/codemirror/typescript/worker/**/*.d.ts"]
}

View file

@ -35,13 +35,13 @@
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@currents/playwright": "^1.15.3",
"@currents/playwright": "catalog:e2e",
"@n8n/api-types": "workspace:*",
"@n8n/constants": "workspace:*",
"@n8n/db": "workspace:*",
"@playwright/test": "1.56.0",
"@playwright/test": "catalog:e2e",
"@types/lodash": "catalog:",
"eslint-plugin-playwright": "2.2.2",
"eslint-plugin-playwright": "catalog:e2e",
"flatted": "catalog:",
"generate-schema": "2.6.0",
"mockserver-client": "^5.15.0",

View file

@ -0,0 +1,55 @@
diff --git a/build/assert.js b/build/assert.js
index b4056fad4efe4508c0bc6dd375334a75ff010d5e..9127af7fa0fce7c3813938f0106f7dd14f4afa0f 100644
--- a/build/assert.js
+++ b/build/assert.js
@@ -37,9 +37,9 @@ var _require = require('./internal/errors'),
ERR_INVALID_RETURN_VALUE = _require$codes.ERR_INVALID_RETURN_VALUE,
ERR_MISSING_ARGS = _require$codes.ERR_MISSING_ARGS;
var AssertionError = require('./internal/assert/assertion_error');
-var _require2 = require('util/'),
+var _require2 = require('util'),
inspect = _require2.inspect;
-var _require$types = require('util/').types,
+var _require$types = require('util').types,
isPromise = _require$types.isPromise,
isRegExp = _require$types.isRegExp;
var objectAssign = require('object.assign/polyfill')();
diff --git a/build/internal/assert/assertion_error.js b/build/internal/assert/assertion_error.js
index d30e83ceb76ae8ac65f094a4df2d8bb4354c2b29..2675cf13b28e5308aa78c2b87b8f5e448b7cc74a 100644
--- a/build/internal/assert/assertion_error.js
+++ b/build/internal/assert/assertion_error.js
@@ -22,7 +22,7 @@ function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[nat
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
-var _require = require('util/'),
+var _require = require('util'),
inspect = _require.inspect;
var _require2 = require('../errors'),
ERR_INVALID_ARG_TYPE = _require2.codes.ERR_INVALID_ARG_TYPE;
diff --git a/build/internal/errors.js b/build/internal/errors.js
index 17b6b4f36d1a7c639cdb144094dcd4e0b3964c2c..cc7d84d069301a3557b8586b297b68f1bea4f257 100644
--- a/build/internal/errors.js
+++ b/build/internal/errors.js
@@ -127,7 +127,7 @@ createErrorType('ERR_INVALID_ARG_TYPE', function (name, expected, actual) {
}, TypeError);
createErrorType('ERR_INVALID_ARG_VALUE', function (name, value) {
var reason = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'is invalid';
- if (util === undefined) util = require('util/');
+ if (util === undefined) util = require('util');
var inspected = util.inspect(value);
if (inspected.length > 128) {
inspected = "".concat(inspected.slice(0, 128), "...");
diff --git a/build/internal/util/comparisons.js b/build/internal/util/comparisons.js
index fd8180d8c95693695a4a04649bae36ba658cea32..2701a6b25766ada28fc04b83a6a07b7648fad515 100644
--- a/build/internal/util/comparisons.js
+++ b/build/internal/util/comparisons.js
@@ -36,7 +36,7 @@ function uncurryThis(f) {
var hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
var propertyIsEnumerable = uncurryThis(Object.prototype.propertyIsEnumerable);
var objectToString = uncurryThis(Object.prototype.toString);
-var _require$types = require('util/').types,
+var _require$types = require('util').types,
isAnyArrayBuffer = _require$types.isAnyArrayBuffer,
isArrayBufferView = _require$types.isArrayBufferView,
isDate = _require$types.isDate,

File diff suppressed because it is too large Load diff

View file

@ -98,6 +98,21 @@ catalogs:
vue-markdown-render: ^2.2.1
vue-router: ^4.5.0
vue-tsc: ^2.2.8
storybook:
'@chromatic-com/storybook': ^4.1.3
'@storybook/addon-a11y': ^10.1.11
'@storybook/addon-docs': ^10.1.11
'@storybook/addon-themes': ^10.1.11
'@storybook/addon-vitest': ^10.1.11
'@storybook/vue3-vite': ^10.1.11
eslint-plugin-storybook: ^10.1.11
storybook: ^10.1.11
e2e:
'@playwright/test': 1.57.0
'@currents/playwright': ^1.15.3
eslint-plugin-playwright: 2.2.2
playwright: 1.57.0
playwright-core: 1.57.0
minimumReleaseAge: 4320