diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 482bc830..87cff673 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -7,29 +7,22 @@ on: jobs: test: - timeout-minutes: 5 + timeout-minutes: 10 runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/playwright:v1.38.0-focal steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 20 - name: Install dependencies run: npm ci - name: Run Playwright tests - run: npx playwright test + run: npm run test:pw:docker env: - VRT_APIURL: "https://visual-regression-tracker.com:4200" - VRT_PROJECT: "VRT" - VRT_ENABLESOFTASSERT: false - VRT_APIKEY: ${{ secrets.VRT_API_KEY }} - VRT_BRANCHNAME: ${{ github.head_ref || github.ref_name }} - VRT_CIBUILDID: "Github run_id: ${{ github.run_id }}" + CI: true - uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 18d8ff5c..a7a65256 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Node.js environment uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 - name: Install npm dependencies run: npm ci diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..5660f81a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d995c5d1..83d49312 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ### STAGE 1: Build ### # This image is around 50 megabytes -FROM node:18-alpine3.18 AS builder +FROM node:20-alpine AS builder # Create app directory WORKDIR /app diff --git a/README.md b/README.md index bbbb4d00..aaf82a69 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,22 @@ The testing related `.spec.tsx` files are used with Playwright for browser tests, and the `.test.tsx` files with Jest for unit tests. +## Running Tests + +### Unit Tests (Jest) +Run Jest unit tests: +```bash +npm run test +``` + +### Integration Tests (Playwright) + +#### Local Testing +Run Playwright tests locally (requires local dev server): +```bash +npm run test:pw:local +``` + ## Local HTTPS config - Generate keys [here](https://www.selfsignedcertificate.com/) diff --git a/env.sh b/env.sh old mode 100644 new mode 100755 diff --git a/integration_tests/fixtures/index.ts b/integration_tests/fixtures/index.ts index c076662d..88fd0f31 100644 --- a/integration_tests/fixtures/index.ts +++ b/integration_tests/fixtures/index.ts @@ -1,5 +1,4 @@ import { test as base } from "@playwright/test"; -import { PlaywrightVisualRegressionTracker } from "@visual-regression-tracker/agent-playwright"; import { LoginPage, ProfilePage, ProjectListPage, ProjectPage } from "pages"; import { RegisterPage } from "pages/RegisterPage"; import { TestVariationDetailsPage } from "pages/TestVariationDetailsPage"; @@ -7,7 +6,6 @@ import { TestVariationListPage } from "pages/TestVariationListPage"; import { UserListPage } from "pages/UserListPage"; type Fixtures = { - vrt: PlaywrightVisualRegressionTracker; loginPage: LoginPage; registerPage: RegisterPage; openProjectPage: ( @@ -28,13 +26,6 @@ type Fixtures = { }; export const test = base.extend({ - vrt: async ({ browserName }, use) => { - const vrt = new PlaywrightVisualRegressionTracker(browserName); - - await vrt.start(); - await use(vrt); - await vrt.stop(); - }, loginPage: [ async ({ page }, use) => { await page.goto("/"); diff --git a/integration_tests/test/login.spec.ts b/integration_tests/test/login.spec.ts index 4b4b7fe3..837bda5e 100644 --- a/integration_tests/test/login.spec.ts +++ b/integration_tests/test/login.spec.ts @@ -1,8 +1,8 @@ import { test } from "fixtures"; import { expect } from "@playwright/test"; -test("renders", async ({ page, vrt }) => { - await vrt.trackPage(page, "Login page"); +test("renders", async ({ page }) => { + await expect(page).toHaveScreenshot("login-page.png"); }); test("can login", async ({ loginPage }) => { diff --git a/integration_tests/test/login.spec.ts-snapshots/login-page.png b/integration_tests/test/login.spec.ts-snapshots/login-page.png new file mode 100644 index 00000000..7c64f74b Binary files /dev/null and b/integration_tests/test/login.spec.ts-snapshots/login-page.png differ diff --git a/integration_tests/test/profile.spec.ts b/integration_tests/test/profile.spec.ts index 1d669b1e..17f2fdc9 100644 --- a/integration_tests/test/profile.spec.ts +++ b/integration_tests/test/profile.spec.ts @@ -1,3 +1,4 @@ +import { expect } from "@playwright/test"; import { test } from "fixtures"; import { TEST_PROJECT } from "~client/_test/test.data.helper"; import { mockGetProjects } from "utils/mocks"; @@ -9,6 +10,6 @@ test.beforeEach(async ({ page }) => { }); // eslint-disable-next-line @typescript-eslint/no-unused-vars -test("renders", async ({ profilePage, page, vrt }) => { - await vrt.trackPage(page, "Profile page"); +test("renders", async ({ profilePage, page }) => { + await expect(page).toHaveScreenshot("profile-page.png"); }); diff --git a/integration_tests/test/profile.spec.ts-snapshots/profile-page.png b/integration_tests/test/profile.spec.ts-snapshots/profile-page.png new file mode 100644 index 00000000..27b317c8 Binary files /dev/null and b/integration_tests/test/profile.spec.ts-snapshots/profile-page.png differ diff --git a/integration_tests/test/projec.spec.ts b/integration_tests/test/projec.spec.ts index 1958c686..ed3236c5 100644 --- a/integration_tests/test/projec.spec.ts +++ b/integration_tests/test/projec.spec.ts @@ -41,15 +41,15 @@ test.beforeEach(async ({ page }) => { await mockImage(page, "image.png"); }); -test("renders", async ({ openProjectPage, page, vrt }) => { +test("renders", async ({ openProjectPage, page }) => { const projectPage = await openProjectPage(project.id); await projectPage.buildList.getBuildLocator(TEST_BUILD_FAILED.number).click(); - await vrt.trackPage(page, "Project page. Test run list"); + await expect(page).toHaveScreenshot("project-page-test-run-list.png"); await projectPage.testRunList.getRow(TEST_UNRESOLVED.id).click(); - await vrt.trackPage(page, "Project page. Test run details"); + await expect(page).toHaveScreenshot("project-page-test-run-details.png"); }); test("can download images", async ({ openProjectPage, page }) => { diff --git a/integration_tests/test/projec.spec.ts-snapshots/project-page-test-run-details.png b/integration_tests/test/projec.spec.ts-snapshots/project-page-test-run-details.png new file mode 100644 index 00000000..77940fb3 Binary files /dev/null and b/integration_tests/test/projec.spec.ts-snapshots/project-page-test-run-details.png differ diff --git a/integration_tests/test/projec.spec.ts-snapshots/project-page-test-run-list.png b/integration_tests/test/projec.spec.ts-snapshots/project-page-test-run-list.png new file mode 100644 index 00000000..4078611b Binary files /dev/null and b/integration_tests/test/projec.spec.ts-snapshots/project-page-test-run-list.png differ diff --git a/integration_tests/test/projectList.spec.ts b/integration_tests/test/projectList.spec.ts index 19e9ca61..2a26f05e 100644 --- a/integration_tests/test/projectList.spec.ts +++ b/integration_tests/test/projectList.spec.ts @@ -10,8 +10,8 @@ test.beforeEach(async ({ page }) => { }); // eslint-disable-next-line @typescript-eslint/no-unused-vars -test("renders", async ({ projectListPage, page, vrt }) => { - await vrt.trackPage(page, "Projects list page"); +test("renders", async ({ projectListPage, page }) => { + await expect(page).toHaveScreenshot("projects-list-page.png"); }); test("can delete project", async ({ projectListPage, page }) => { diff --git a/integration_tests/test/projectList.spec.ts-snapshots/projects-list-page.png b/integration_tests/test/projectList.spec.ts-snapshots/projects-list-page.png new file mode 100644 index 00000000..e81e593a Binary files /dev/null and b/integration_tests/test/projectList.spec.ts-snapshots/projects-list-page.png differ diff --git a/integration_tests/test/register.spec.ts b/integration_tests/test/register.spec.ts index e2055202..971b400e 100644 --- a/integration_tests/test/register.spec.ts +++ b/integration_tests/test/register.spec.ts @@ -2,8 +2,8 @@ import { test } from "fixtures"; import { expect } from "@playwright/test"; // eslint-disable-next-line @typescript-eslint/no-unused-vars -test("renders", async ({ registerPage, page, vrt }) => { - await vrt.trackPage(page, "Register page"); +test("renders", async ({ registerPage, page }) => { + await expect(page).toHaveScreenshot("register-page.png"); }); test("can register", async ({ registerPage }) => { diff --git a/integration_tests/test/register.spec.ts-snapshots/register-page.png b/integration_tests/test/register.spec.ts-snapshots/register-page.png new file mode 100644 index 00000000..4cce755d Binary files /dev/null and b/integration_tests/test/register.spec.ts-snapshots/register-page.png differ diff --git a/integration_tests/test/testVariationDetails.spec.ts b/integration_tests/test/testVariationDetails.spec.ts index 1302b7cf..90b9a97b 100644 --- a/integration_tests/test/testVariationDetails.spec.ts +++ b/integration_tests/test/testVariationDetails.spec.ts @@ -1,3 +1,4 @@ +import { expect } from "@playwright/test"; import { test } from "fixtures"; import { mockGetProjects, @@ -16,10 +17,10 @@ test.beforeEach(async ({ page }) => { await mockImage(page, "baseline2.png"); }); -test("renders", async ({ openTestVariationDetailsPage, page, vrt }) => { +test("renders", async ({ openTestVariationDetailsPage, page }) => { await openTestVariationDetailsPage(TEST_VARIATION_ONE.id); - await vrt.trackPage(page, "TestVariationDetails page", { - screenshotOptions: { fullPage: true }, + await expect(page).toHaveScreenshot("test-variation-details-page.png", { + fullPage: true, }); }); diff --git a/integration_tests/test/testVariationDetails.spec.ts-snapshots/test-variation-details-page.png b/integration_tests/test/testVariationDetails.spec.ts-snapshots/test-variation-details-page.png new file mode 100644 index 00000000..34bf92af Binary files /dev/null and b/integration_tests/test/testVariationDetails.spec.ts-snapshots/test-variation-details-page.png differ diff --git a/integration_tests/test/testVariationList.spec.ts b/integration_tests/test/testVariationList.spec.ts index ec6d79cf..908cae2e 100644 --- a/integration_tests/test/testVariationList.spec.ts +++ b/integration_tests/test/testVariationList.spec.ts @@ -1,3 +1,4 @@ +import { expect } from "@playwright/test"; import { test } from "fixtures"; import { mockGetProjects, mockGetTestVariations, mockImage } from "utils/mocks"; import { @@ -16,8 +17,8 @@ test.beforeEach(async ({ page }) => { await mockImage(page, "baseline2.png"); }); -test("renders", async ({ openTestVariationListPage, page, vrt }) => { +test("renders", async ({ openTestVariationListPage, page }) => { await openTestVariationListPage(TEST_PROJECT.id); - await vrt.trackPage(page, "TestVariationList page"); + await expect(page).toHaveScreenshot("test-variation-list-page.png"); }); diff --git a/integration_tests/test/testVariationList.spec.ts-snapshots/test-variation-list-page.png b/integration_tests/test/testVariationList.spec.ts-snapshots/test-variation-list-page.png new file mode 100644 index 00000000..bb933a4c Binary files /dev/null and b/integration_tests/test/testVariationList.spec.ts-snapshots/test-variation-list-page.png differ diff --git a/integration_tests/test/userList.spec.ts b/integration_tests/test/userList.spec.ts index 0da5cf63..e8fb0fce 100644 --- a/integration_tests/test/userList.spec.ts +++ b/integration_tests/test/userList.spec.ts @@ -15,8 +15,8 @@ test.beforeEach(async ({ page }) => { }); // eslint-disable-next-line @typescript-eslint/no-unused-vars -test("renders", async ({ userListPage, page, vrt }) => { - await vrt.trackPage(page, "User list page"); +test("renders", async ({ userListPage, page }) => { + await expect(page).toHaveScreenshot("user-list-page.png"); }); const assignRoleCases: [User, keyof typeof Role][] = [ diff --git a/integration_tests/test/userList.spec.ts-snapshots/user-list-page.png b/integration_tests/test/userList.spec.ts-snapshots/user-list-page.png new file mode 100644 index 00000000..69dc3c9e Binary files /dev/null and b/integration_tests/test/userList.spec.ts-snapshots/user-list-page.png differ diff --git a/package-lock.json b/package-lock.json index 60bcce1b..84f32d72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@playwright/test": "^1.38.0", + "@playwright/test": "^1.57.0", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^12.8.3", @@ -52,7 +52,6 @@ "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", - "@visual-regression-tracker/agent-playwright": "^5.3.0", "@vitejs/plugin-react": "^4.0.4", "eslint": "^8.49.0", "eslint-config-prettier": "^9.0.0", @@ -72,7 +71,7 @@ "vite": "^4.5.3" }, "engines": { - "node": ">=16" + "node": ">=20" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -127,6 +126,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -698,6 +698,7 @@ "node_modules/@emotion/react": { "version": "11.11.1", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -735,6 +736,7 @@ "node_modules/@emotion/styled": { "version": "11.11.0", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -772,6 +774,74 @@ "version": "0.3.1", "license": "MIT" }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.11.tgz", + "integrity": "sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz", + "integrity": "sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.11.tgz", + "integrity": "sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz", + "integrity": "sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-x64": { "version": "0.18.11", "cpu": [ @@ -787,6 +857,295 @@ "node": ">=12" } }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz", + "integrity": "sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz", + "integrity": "sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz", + "integrity": "sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz", + "integrity": "sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz", + "integrity": "sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz", + "integrity": "sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz", + "integrity": "sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz", + "integrity": "sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz", + "integrity": "sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz", + "integrity": "sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz", + "integrity": "sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz", + "integrity": "sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz", + "integrity": "sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz", + "integrity": "sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz", + "integrity": "sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz", + "integrity": "sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz", + "integrity": "sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -1341,6 +1700,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -1747,7 +2107,6 @@ "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "optional": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -1849,6 +2208,7 @@ "version": "5.14.10", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.10.tgz", "integrity": "sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==", + "peer": true, "dependencies": { "@babel/runtime": "^7.22.15", "@mui/base": "5.0.0-beta.16", @@ -2010,6 +2370,7 @@ "version": "5.14.10", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.10.tgz", "integrity": "sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.22.15", "@mui/private-theming": "^5.14.10", @@ -2163,18 +2524,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", - "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.38.0" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@popperjs/core": { @@ -2556,6 +2918,7 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", "dev": true, + "peer": true, "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -2624,7 +2987,8 @@ "version": "18.17.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.17.tgz", "integrity": "sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -2644,6 +3008,7 @@ "version": "17.0.65", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.65.tgz", "integrity": "sha512-oxur785xZYHvnI7TRS61dXbkIhDPnGfsXKv0cNXR/0ml4SipRIFpSMzA7HMEfOywFwJ5AOnPrXYTEiTRUQeGlQ==", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2796,6 +3161,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.2.tgz", "integrity": "sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.7.2", "@typescript-eslint/types": "6.7.2", @@ -2945,33 +3311,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@visual-regression-tracker/agent-playwright": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@visual-regression-tracker/agent-playwright/-/agent-playwright-5.3.1.tgz", - "integrity": "sha512-sy9sT86Tp8QfXMzojgZ9V3MxoFMZttVSrPmV78Gl0Hun3mF50uUhtXwV/64SwdJcVUnaostyIaombIluuhVH0Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@visual-regression-tracker/sdk-js": "^5.7.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@visual-regression-tracker/sdk-js": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@visual-regression-tracker/sdk-js/-/sdk-js-5.7.1.tgz", - "integrity": "sha512-6EnyTX5Fd6uhcWIUwsVtPmPdlpr5D/s9m0Yxo0Hk/yI+/y0uSil34ulVNKXjaKGin7sZDzoj5CQe8ysl+Z/XfQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "axios": "^1.6.2", - "form-data": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@vitejs/plugin-react": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", @@ -3007,6 +3346,7 @@ "version": "8.10.0", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3293,18 +3633,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { "version": "3.2.1", "dev": true, @@ -3523,6 +3851,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001517", "electron-to-chromium": "^1.4.477", @@ -3979,7 +4308,8 @@ }, "node_modules/csstype": { "version": "3.1.2", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -4438,6 +4768,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4571,6 +4902,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.findlastindex": "^1.2.2", @@ -5105,27 +5437,6 @@ "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "dev": true, @@ -6100,6 +6411,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8221,7 +8533,8 @@ "type": "github", "url": "https://github.com/sponsors/lavrton" } - ] + ], + "peer": true }, "node_modules/language-subtag-registry": { "version": "0.3.22", @@ -9287,33 +9600,35 @@ } }, "node_modules/playwright": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", - "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.38.0" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/postcss": { @@ -9415,6 +9730,7 @@ "node_modules/prop-types": { "version": "15.8.1", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -9425,13 +9741,6 @@ "version": "16.13.1", "license": "MIT" }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, "node_modules/psl": { "version": "1.8.0", "dev": true, @@ -9501,6 +9810,7 @@ "node_modules/react": { "version": "17.0.2", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -9523,6 +9833,7 @@ "node_modules/react-dom": { "version": "17.0.2", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -10188,7 +10499,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -10199,7 +10509,6 @@ "dev": true, "license": "BSD-3-Clause", "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10429,7 +10738,6 @@ "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", "dev": true, "optional": true, - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10447,8 +10755,7 @@ "version": "2.20.3", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/test-exclude": { "version": "6.0.0", @@ -10577,6 +10884,7 @@ "version": "10.9.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -10737,6 +11045,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10855,6 +11164,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -11276,6 +11586,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -11683,6 +11994,7 @@ }, "@emotion/react": { "version": "11.11.1", + "peer": true, "requires": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -11709,6 +12021,7 @@ }, "@emotion/styled": { "version": "11.11.0", + "peer": true, "requires": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -11731,11 +12044,158 @@ "@emotion/weak-memoize": { "version": "0.3.1" }, + "@esbuild/android-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.11.tgz", + "integrity": "sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz", + "integrity": "sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.11.tgz", + "integrity": "sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz", + "integrity": "sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==", + "dev": true, + "optional": true + }, "@esbuild/darwin-x64": { "version": "0.18.11", "dev": true, "optional": true }, + "@esbuild/freebsd-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz", + "integrity": "sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz", + "integrity": "sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz", + "integrity": "sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz", + "integrity": "sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz", + "integrity": "sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz", + "integrity": "sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz", + "integrity": "sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz", + "integrity": "sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz", + "integrity": "sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz", + "integrity": "sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz", + "integrity": "sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz", + "integrity": "sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz", + "integrity": "sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz", + "integrity": "sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz", + "integrity": "sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz", + "integrity": "sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz", + "integrity": "sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==", + "dev": true, + "optional": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -12140,6 +12600,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, + "peer": true, "requires": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -12429,7 +12890,6 @@ "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "optional": true, - "peer": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -12491,6 +12951,7 @@ "version": "5.14.10", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.10.tgz", "integrity": "sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==", + "peer": true, "requires": { "@babel/runtime": "^7.22.15", "@mui/base": "5.0.0-beta.16", @@ -12572,6 +13033,7 @@ "version": "5.14.10", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.10.tgz", "integrity": "sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==", + "peer": true, "requires": { "@babel/runtime": "^7.22.15", "@mui/private-theming": "^5.14.10", @@ -12652,12 +13114,12 @@ } }, "@playwright/test": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", - "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "requires": { - "playwright": "1.38.0" + "playwright": "1.57.0" } }, "@popperjs/core": { @@ -12924,6 +13386,7 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", "dev": true, + "peer": true, "requires": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -12979,7 +13442,8 @@ "version": "18.17.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.17.tgz", "integrity": "sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==", - "dev": true + "dev": true, + "peer": true }, "@types/parse-json": { "version": "4.0.0" @@ -12997,6 +13461,7 @@ "version": "17.0.65", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.65.tgz", "integrity": "sha512-oxur785xZYHvnI7TRS61dXbkIhDPnGfsXKv0cNXR/0ml4SipRIFpSMzA7HMEfOywFwJ5AOnPrXYTEiTRUQeGlQ==", + "peer": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -13121,6 +13586,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.2.tgz", "integrity": "sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "6.7.2", "@typescript-eslint/types": "6.7.2", @@ -13197,25 +13663,6 @@ "eslint-visitor-keys": "^3.4.1" } }, - "@visual-regression-tracker/agent-playwright": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@visual-regression-tracker/agent-playwright/-/agent-playwright-5.3.1.tgz", - "integrity": "sha512-sy9sT86Tp8QfXMzojgZ9V3MxoFMZttVSrPmV78Gl0Hun3mF50uUhtXwV/64SwdJcVUnaostyIaombIluuhVH0Q==", - "dev": true, - "requires": { - "@visual-regression-tracker/sdk-js": "^5.7.1" - } - }, - "@visual-regression-tracker/sdk-js": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@visual-regression-tracker/sdk-js/-/sdk-js-5.7.1.tgz", - "integrity": "sha512-6EnyTX5Fd6uhcWIUwsVtPmPdlpr5D/s9m0Yxo0Hk/yI+/y0uSil34ulVNKXjaKGin7sZDzoj5CQe8ysl+Z/XfQ==", - "dev": true, - "requires": { - "axios": "^1.6.2", - "form-data": "^4.0.0" - } - }, "@vitejs/plugin-react": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", @@ -13240,7 +13687,8 @@ }, "acorn": { "version": "8.10.0", - "dev": true + "dev": true, + "peer": true }, "acorn-globals": { "version": "7.0.1", @@ -13432,17 +13880,6 @@ "version": "4.7.2", "dev": true }, - "axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", - "dev": true, - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "axobject-query": { "version": "3.2.1", "dev": true, @@ -13587,6 +14024,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001517", "electron-to-chromium": "^1.4.477", @@ -13908,7 +14346,8 @@ } }, "csstype": { - "version": "3.1.2" + "version": "3.1.2", + "peer": true }, "damerau-levenshtein": { "version": "1.0.8", @@ -14227,6 +14666,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -14378,6 +14818,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.6", "array.prototype.findlastindex": "^1.2.2", @@ -14695,12 +15136,6 @@ "version": "3.2.7", "dev": true }, - "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true - }, "for-each": { "version": "0.3.3", "dev": true, @@ -15314,6 +15749,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "requires": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -16879,7 +17315,8 @@ "konva": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/konva/-/konva-7.2.5.tgz", - "integrity": "sha512-yk/li8rUF+09QNlOdkwbEId+QvfATMe/aMGVouWW1oFoUVTYWHsQuIAE6lWy11DK8mLJEJijkNAXC5K+NVlMew==" + "integrity": "sha512-yk/li8rUF+09QNlOdkwbEId+QvfATMe/aMGVouWW1oFoUVTYWHsQuIAE6lWy11DK8mLJEJijkNAXC5K+NVlMew==", + "peer": true }, "language-subtag-registry": { "version": "0.3.22", @@ -17538,19 +17975,19 @@ } }, "playwright": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", - "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, "requires": { "fsevents": "2.3.2", - "playwright-core": "1.38.0" + "playwright-core": "1.57.0" } }, "playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true }, "postcss": { @@ -17607,6 +18044,7 @@ }, "prop-types": { "version": "15.8.1", + "peer": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -17618,12 +18056,6 @@ } } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "psl": { "version": "1.8.0", "dev": true @@ -17654,6 +18086,7 @@ }, "react": { "version": "17.0.2", + "peer": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -17668,6 +18101,7 @@ }, "react-dom": { "version": "17.0.2", + "peer": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -18096,7 +18530,6 @@ "version": "0.5.21", "dev": true, "optional": true, - "peer": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -18105,8 +18538,7 @@ "source-map": { "version": "0.6.1", "dev": true, - "optional": true, - "peer": true + "optional": true } } }, @@ -18261,7 +18693,6 @@ "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", "dev": true, "optional": true, - "peer": true, "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -18272,8 +18703,7 @@ "commander": { "version": "2.20.3", "dev": true, - "optional": true, - "peer": true + "optional": true } } }, @@ -18352,6 +18782,7 @@ "ts-node": { "version": "10.9.1", "dev": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -18453,7 +18884,8 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true + "dev": true, + "peer": true }, "unbox-primitive": { "version": "1.0.2", @@ -18524,6 +18956,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, + "peer": true, "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", diff --git a/package.json b/package.json index 309258d4..b3f9ef00 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "type": "module", "engines": { - "node": ">=16" + "node": ">=20" }, "dependencies": { "@babel/runtime": "^7.22.15", @@ -41,9 +41,11 @@ "build": "vite build", "lint": "eslint --ignore-path .gitignore . --ext .ts,.tsx,.jsx,.js", "typescheck": "tsc --noEmit", - "test:pw:install": "npx playwright install chromium", - "test:pw:local": "npm run test:pw:install && VRT_CIBUILDID=$(date +%H:%M:%S_%d-%m-%Y) playwright test", - "test:pw:debug": "npm run test:pw:install && PWDEBUG=1 playwright test" + "test:pw:install": "NODE_TLS_REJECT_UNAUTHORIZED=0 npx playwright install --with-deps chromium", + "test:pw:local": "npm run test:pw:install && playwright test", + "test:pw:debug": "npm run test:pw:install && PWDEBUG=1 playwright test", + "test:pw:docker": "chmod +x ./test-docker.sh && ./test-docker.sh", + "test:pw:docker:update": "chmod +x ./test-docker.sh && ./test-docker.sh --update-snapshots" }, "browserslist": [ ">0.3%", @@ -62,7 +64,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.38.0", + "@playwright/test": "^1.57.0", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^12.8.3", @@ -78,7 +80,6 @@ "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", - "@visual-regression-tracker/agent-playwright": "^5.3.0", "@vitejs/plugin-react": "^4.0.4", "eslint": "^8.49.0", "eslint-config-prettier": "^9.0.0", diff --git a/playwright.config.ts b/playwright.config.ts index f4e3b673..c8ecd6d4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from "@playwright/test"; const baseURL = "http://localhost:5173"; +const isCI = !!process.env.CI; export default defineConfig({ testDir: "integration_tests/test", @@ -17,7 +18,15 @@ export default defineConfig({ actionTimeout: 5000, navigationTimeout: 5000, trace: "retry-with-trace", + screenshot: "only-on-failure", }, - retries: process.env.CI ? 1 : 0, - forbidOnly: !!process.env.CI, + expect: { + toHaveScreenshot: { + threshold: 0.2, + maxDiffPixels: 100, + }, + }, + snapshotPathTemplate: "{testDir}/{testFileName}-snapshots/{arg}{ext}", + retries: isCI ? 1 : 0, + forbidOnly: isCI, }); diff --git a/src/components/ProjectForm/ProjectForm.tsx b/src/components/ProjectForm/ProjectForm.tsx index f9754b73..78cde6d8 100644 --- a/src/components/ProjectForm/ProjectForm.tsx +++ b/src/components/ProjectForm/ProjectForm.tsx @@ -18,6 +18,7 @@ import { ImageComparison } from "../../types/imageComparison"; import { LooksSameConfigForm } from "./LooksSameConfigForm"; import { OdiffConfigForm } from "./OdiffConfigForm"; import { PixelmatchConfigForm } from "./PixelmatchConfigForm"; +import { VlmConfigForm } from "./VlmConfigForm"; import { getDefaultConfig } from "./utils"; export const ProjectForm: React.FunctionComponent = () => { @@ -35,6 +36,9 @@ export const ProjectForm: React.FunctionComponent = () => { case ImageComparison.odiff: return ; + case ImageComparison.vlm: + return ; + default: return null; } @@ -177,6 +181,9 @@ export const ProjectForm: React.FunctionComponent = () => { {ImageComparison.odiff} + + {ImageComparison.vlm} + {config} diff --git a/src/components/ProjectForm/VlmConfigForm.tsx b/src/components/ProjectForm/VlmConfigForm.tsx new file mode 100644 index 00000000..f1e0e1a2 --- /dev/null +++ b/src/components/ProjectForm/VlmConfigForm.tsx @@ -0,0 +1,201 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { LinearProgress, FormControl, InputLabel, Select, MenuItem, FormHelperText, SelectChangeEvent, FormControlLabel, Switch } from "@mui/material"; +import { TextValidator } from "react-material-ui-form-validator"; +import { useSnackbar } from "notistack"; +import { VlmConfig } from "../../types/imageComparison"; +import { Tooltip } from "../Tooltip"; +import { useConfigHook } from "./useConfigHook"; +import { ollamaService } from "../../services"; +import { OllamaModel } from "../../types"; + +export const VlmConfigForm: React.FunctionComponent = () => { + const { enqueueSnackbar } = useSnackbar(); + const [config, updateConfig] = useConfigHook(); + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + let isMounted = true; + setLoading(true); + ollamaService + .listModels() + .then((fetchedModels) => { + if (isMounted) { + setModels(fetchedModels); + } + }) + .catch((err) => { + if (isMounted) { + enqueueSnackbar(err, { + variant: "error", + }); + } + }) + .finally(() => { + if (isMounted) { + setLoading(false); + } + }); + return () => { + isMounted = false; + }; + }, [enqueueSnackbar]); + + const hasError = useMemo(() => { + if (!config.model || loading || models.length === 0) { + return false; + } + return !models.some((m) => m.name === config.model); + }, [config.model, models, loading]); + + const handleModelChange = (event: SelectChangeEvent) => { + updateConfig("model", event.target.value); + }; + + const renderModelField = () => { + if (loading) { + return ( + + ); + } + + if (models.length === 0) { + return ( + ) => { + updateConfig("model", event.target.value); + }} + /> + ); + } + + return ( + + Model + + {hasError && ( + Selected model is not in the available list. + )} + + ); + }; + + return ( + + +
+ {renderModelField()} + +
+
+ {loading && } + +
+ ) => { + updateConfig("prompt", event.target.value); + }} + /> +
+
+ +
+ ) => { + const { value } = event.target; + updateConfig("temperature", parseFloat(value)); + }} + /> +
+
+ + + updateConfig("useThinking", checked) + } + color="primary" + name="useThinking" + /> + } + /> + +
+ ); +}; + diff --git a/src/components/ProjectForm/utils.ts b/src/components/ProjectForm/utils.ts index e1438628..4b7c4dd2 100644 --- a/src/components/ProjectForm/utils.ts +++ b/src/components/ProjectForm/utils.ts @@ -2,6 +2,7 @@ import { LOOKSSAME_DEFAULT_CONFIG, ODIFF_DEFAULT_CONFIG, PIXELMATCH_DEFAULT_CONFIG, + VLM_DEFAULT_CONFIG, } from "../../constants"; import { ImageComparison } from "../../types/imageComparison"; @@ -13,6 +14,8 @@ export const getDefaultConfig = (imageComparison: ImageComparison): string => { return LOOKSSAME_DEFAULT_CONFIG; case ImageComparison.odiff: return ODIFF_DEFAULT_CONFIG; + case ImageComparison.vlm: + return VLM_DEFAULT_CONFIG; default: return PIXELMATCH_DEFAULT_CONFIG; } diff --git a/src/components/TestDetailsDialog/TestRunDetails.tsx b/src/components/TestDetailsDialog/TestRunDetails.tsx index 2a1e57f4..bd67aa34 100644 --- a/src/components/TestDetailsDialog/TestRunDetails.tsx +++ b/src/components/TestDetailsDialog/TestRunDetails.tsx @@ -1,56 +1,96 @@ import React from "react"; -import { Grid, Typography } from "@mui/material"; +import { Grid, Typography, Paper, type Theme } from "@mui/material"; +import { makeStyles } from "@mui/styles"; import { TestRun } from "../../types"; import { Tooltip } from "../Tooltip"; +const useStyles = makeStyles((theme: Theme) => ({ + vlmContainer: { + padding: theme.spacing(1.5), + marginTop: theme.spacing(1), + backgroundColor: "rgba(0, 0, 0, 0.02)", + borderLeft: "3px solid", + borderColor: theme.palette.primary.main, + }, + vlmLabel: { + fontWeight: 600, + display: "block", + marginBottom: theme.spacing(0.5), + color: theme.palette.text.secondary, + }, + vlmDescription: { + whiteSpace: "pre-wrap", + wordBreak: "break-word", + lineHeight: 1.6, + }, +})); + interface IProps { testRun: TestRun; } export const TestRunDetails: React.FunctionComponent = ({ testRun, -}) => ( - - {testRun.os && ( - - OS: {testRun.os} - - )} - {testRun.device && ( - - Device: {testRun.device} - - )} - {testRun.browser && ( - - Browser: {testRun.browser} - - )} - {testRun.viewport && ( +}) => { + const classes = useStyles(); + + return ( + + {testRun.os && ( + + OS: {testRun.os} + + )} + {testRun.device && ( + + Device: {testRun.device} + + )} + {testRun.browser && ( + + Browser: {testRun.browser} + + )} + {testRun.viewport && ( + + Viewport: {testRun.viewport} + + )} + {testRun.customTags && ( + + + Custom Tags: {testRun.customTags} + + + )} - Viewport: {testRun.viewport} + + + Diff: {Math.round(testRun.diffPercent * 100) / 100}% + + - )} - {testRun.customTags && ( - - Custom Tags: {testRun.customTags} - + + + Diff tolerance: {testRun.diffTollerancePercent}% + + - )} - - - - Diff: {Math.round(testRun.diffPercent * 100) / 100}% - - - - - - - Diff tolerance: {testRun.diffTollerancePercent}% - - - - -); + {testRun.vlmDescription && ( + + + + + VLM Analysis: + + + {testRun.vlmDescription} + + + + + )} + + ); +}; diff --git a/src/constants/project.ts b/src/constants/project.ts index 13fa1930..a521ae4b 100644 --- a/src/constants/project.ts +++ b/src/constants/project.ts @@ -7,6 +7,19 @@ export const LOOKSSAME_DEFAULT_CONFIG = '{"strict":false,"tolerance":2.3,"ignoreAntialiasing":true,"antialiasingTolerance":0,"ignoreCaret":true,"allowDiffDimensions":false}'; export const ODIFF_DEFAULT_CONFIG = '{"threshold":0,"antialiasing":true,"failOnLayoutDiff":true,"outputDiffMask":true}'; +export const VLM_DEFAULT_CONFIG = JSON.stringify({ + model: "", + prompt: `You are provided with three images: +1. First image: baseline screenshot +2. Second image: new version screenshot +3. Diff image + +Spot any difference in text, color, shape and position of elements - treat as different even slight change. +Ignore minor rendering artifacts that are imperceptible to users like antialiasing. +Describe the difference in about 100 words.`, + temperature: 0.1, + useThinking: false, +}); export const DEFAULT_PROJECT_EDIT_STATE: ProjectDto = { id: "", diff --git a/src/services/index.ts b/src/services/index.ts index 4d33a69b..9be90037 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,3 +4,4 @@ export * from "./builds.service"; export * from "./static.service"; export * from "./testVariation.service"; export * from "./testRun.service"; +export * from "./ollama.service"; diff --git a/src/services/ollama.service.ts b/src/services/ollama.service.ts new file mode 100644 index 00000000..b8ae2215 --- /dev/null +++ b/src/services/ollama.service.ts @@ -0,0 +1,24 @@ +import { handleResponse, authHeader } from "../_helpers/service.helpers"; +import { API_URL } from "../_config/env.config"; +import { OllamaModel } from "../types"; + +interface OllamaModelsResponse { + models: OllamaModel[]; +} + +async function listModels(): Promise { + const requestOptions = { + method: "GET", + headers: authHeader(), + }; + + const response = await fetch(`${API_URL}/ollama/models`, requestOptions); + const data: OllamaModelsResponse = await handleResponse(response); + return data.models; +} + +export const ollamaService = { + listModels, +}; + + diff --git a/src/services/static.service.ts b/src/services/static.service.ts index 38c2db87..62d3b2d1 100644 --- a/src/services/static.service.ts +++ b/src/services/static.service.ts @@ -1,7 +1,6 @@ import { API_URL } from "../_config/env.config"; import noImage from "../static/no-image.png"; import JSZip from "jszip"; -import axios from "axios"; import FileSaver from "file-saver"; function getImage(name: string): string { @@ -16,17 +15,15 @@ async function downloadAsZip( }[], ): Promise { const zip = new JSZip(); - const downloadFilePromises = items.map((item) => - axios.get(item.url, { responseType: "blob" }).then((resp) => { - zip.file(item.filename.concat(".png"), resp.data); - }), - ); - - return Promise.all(downloadFilePromises).then(() => { - zip.generateAsync({ type: "blob" }).then((blob) => { - FileSaver.saveAs(blob, "vrt_images.zip"); - }); + const downloadFilePromises = items.map(async (item) => { + const response = await fetch(item.url); + const blob = await response.blob(); + zip.file(item.filename.concat(".png"), blob); }); + + await Promise.all(downloadFilePromises); + const zipBlob = await zip.generateAsync({ type: "blob" }); + FileSaver.saveAs(zipBlob, "vrt_images.zip"); } export const staticService = { diff --git a/src/types/imageComparison.ts b/src/types/imageComparison.ts index 8634ecae..f39ee57f 100644 --- a/src/types/imageComparison.ts +++ b/src/types/imageComparison.ts @@ -1,12 +1,14 @@ export type ImageComparisonConfig = | PixelmatchConfig | LooksSameConfig - | OdiffConfig; + | OdiffConfig + | VlmConfig; export enum ImageComparison { pixelmatch = "pixelmatch", lookSame = "lookSame", odiff = "odiff", + vlm = "vlm", } export interface PixelmatchConfig { @@ -49,3 +51,10 @@ export interface OdiffConfig { /** If this is true, antialiased pixels are not counted to the diff of an image */ antialiasing: boolean; } + +export interface VlmConfig { + model: string; + prompt: string; + temperature: number; + useThinking?: boolean; +} diff --git a/src/types/index.ts b/src/types/index.ts index 98ca6b70..d862c49d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,3 +6,4 @@ export * from "./testVariation"; export * from "./testStatus"; export * from "./paginatedData"; export * from "./imageComparison"; +export * from "./ollama"; diff --git a/src/types/ollama.ts b/src/types/ollama.ts new file mode 100644 index 00000000..028ac97e --- /dev/null +++ b/src/types/ollama.ts @@ -0,0 +1,7 @@ +export interface OllamaModel { + name: string; + size?: number; + digest?: string; + modified_at?: string; +} + diff --git a/src/types/testRun.ts b/src/types/testRun.ts index c26406f6..da0da6d5 100644 --- a/src/types/testRun.ts +++ b/src/types/testRun.ts @@ -22,4 +22,5 @@ export interface TestRun { branchName: string; baselineBranchName: string; merge: boolean; + vlmDescription?: string; } diff --git a/test-docker.sh b/test-docker.sh new file mode 100755 index 00000000..d912ac4a --- /dev/null +++ b/test-docker.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +# Get Playwright version from package.json +PLAYWRIGHT_VERSION=$(node -p "require('./package.json').devDependencies['@playwright/test']?.replace(/[^\d.]/g, '')") +IMAGE_NAME="mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}" + +# Get current directory for mounting +LOCAL_DIR=$(pwd) +DOCKER_DIR="${LOCAL_DIR}" + +echo "Pulling image ${IMAGE_NAME}..." +docker pull "${IMAGE_NAME}" + +# Build command - install Linux-compatible dependencies, then run tests +# This ensures platform-specific binaries (like esbuild) are installed for Linux +PLAYWRIGHT_ARGS="$*" +COMMAND=("bash" "-c" "npm install && npx playwright test ${PLAYWRIGHT_ARGS}") + +echo "Creating container with command: npm install && npx playwright test ${PLAYWRIGHT_ARGS}..." + +# Run the container +# Note: Since web server and tests run in the same container, localhost connections work +EXIT_CODE=0 +docker run --rm \ + --ipc=host \ + -v "${LOCAL_DIR}:${DOCKER_DIR}" \ + -w "${DOCKER_DIR}" \ + "${ENV_ARGS[@]}" \ + "${IMAGE_NAME}" \ + "${COMMAND[@]}" || EXIT_CODE=$? + +echo "Container exit code: ${EXIT_CODE}" + +if [ $EXIT_CODE -ne 0 ]; then + echo "Container ended unsuccessfully, check logs for info" >&2 + exit $EXIT_CODE +fi diff --git a/vite.config.ts b/vite.config.ts index bd5c6830..02d3dfd7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,8 +11,7 @@ export default defineConfig({ }, }, optimizeDeps: { - // Enable using "npm link" for this package when developing - include: ["@visual-regression-tracker/agent-playwright"], + // Dependencies to pre-bundle }, build: { outDir: "build",