diff --git a/.github/instructions/general.instructions.md b/.github/instructions/general.instructions.md index 78b13485c..61e3a5096 100644 --- a/.github/instructions/general.instructions.md +++ b/.github/instructions/general.instructions.md @@ -35,6 +35,7 @@ applyTo: "**" - JavaScript loading warnings from happy-dom are silenced in vitest.setup.ts for clean test output. - A working example test using the Container API is available at /home/kevin/Repos/Webstack Builders/Corporate Website/astro.webstackbuilders.com/src/components/Test/container.spec.ts - **NEVER hard-code content slugs in e2e tests** (e.g., `/articles/typescript-best-practices`, `/services/web-development`). Content can be deleted or renamed. Always dynamically fetch the first available item from listing pages (articles, services, case-studies, etc.) and navigate to it. This prevents test breakage when content changes. +- **Playwright E2E Tests**: Set `DEBUG=1` environment variable to prevent the dev server from being launched by the Playwright test runner. This is useful when you want to run tests against an already running dev server. # Personality - Do not apologize diff --git a/.vscode/settings.json b/.vscode/settings.json index a022869e3..891d63a94 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,7 @@ "dango", "dbtable", "Dynatrace", + "ecommerce", "Favicons", "Fedi", "Fedibird", diff --git a/TODO.md b/TODO.md index e15bd8344..a3d315998 100644 --- a/TODO.md +++ b/TODO.md @@ -36,6 +36,11 @@ https://github.com/modernweb-dev/rocket/tree/main/packages/check-html-links "Scaling/zooming animations are problematic for accessibility, as they are a common trigger for certain types of migraine. If you need to include such animations on your website, you should provide a control to allow users to turn off animations, preferably site-wide. Also, consider making use of the prefers-reduced-motion media feature — use it to write a media query that will turn off animations if the user has reduced animation specified in their system preferences. " +## Fix offline page + +- It should look like any other page, but have interactivity like navigation and other links disabled. +- The test cases should check for if they're on the offline page, and return passed for an inverse. Like the smoke test that makes sure navigation is available, it would return true if navigation is present but disabled in the same test case. + ## @TODO: Handle `@media (prefers-reduced-motion: reduce)` Stop the Hero Greensocks animation when `@media (prefers-reduced-motion: reduce)`, using `window.mediaQuery()`. Handle user preference for reduced motion on animations, doing this also with a listener like for browser theme preference @@ -573,3 +578,27 @@ Repo is in root of Corporate Websites * [**typographic-permille**][typographic-permille] Micro module to replace `%o` with `‰` and optionally replace the preceding space. + +## Tests + +```typescript +test('Critical Paths @smoke', async ({ context, page, allPaths }) => { + for (const path of allPaths) { + await test.step('@ready all main pages are accessible', async () => { + await page.goto(path) + await expect(page.locator('main')).toBeVisible() + }) + + await test.step('@ready cookie consent banner appears', async () => { + // Clear consent cookies to force banner to appear + await clearConsentCookies(context) + + await page.goto(path) + await page.waitForLoadState('networkidle') + + // Cookie modal should be visible + await expect(page.locator('#cookie-modal-id')).toBeVisible() + }) + } +}) +``` diff --git a/astro.config.ts b/astro.config.ts index 8c6541309..876807335 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -1,5 +1,6 @@ import mdx from '@astrojs/mdx' -import preact from "@astrojs/preact" +import preact from '@astrojs/preact' +import sitemap from '@astrojs/sitemap' import vercelStatic from '@astrojs/vercel' import sentry from "@sentry/astro" import tailwindcss from '@tailwindcss/vite' @@ -14,6 +15,7 @@ import { vercelConfig, } from './src/lib/config' import { callToActionValidator } from './src/integrations/CtaValidator/call-to-action-validator' +import { serializeSitemapItem, writePagesJson } from './src/lib/config/sitemap-serialize' // Type guard for required environment variables (only in CI) const IS_CI = process.env['CI'] === 'true' @@ -42,6 +44,19 @@ export default defineConfig({ org: "webstack-builders", authToken: SENTRY_AUTH_TOKEN, })] : []), + sitemap({ + lastmod: new Date(), + serialize: serializeSitemapItem, + }), + // Custom integration to write pages.json after build + { + name: 'pages-json-writer', + hooks: { + 'astro:build:done': () => { + writePagesJson() + }, + }, + }, ], output: 'static', prefetch: true, diff --git a/package-lock.json b/package-lock.json index 89ddd4d4c..5a8952c65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,11 @@ "version": "0.0.1", "license": "AGPL-3.0", "dependencies": { - "@astrojs/check": "0.9.4", + "@astrojs/check": "0.9.5", "@astrojs/mdx": "4.3.7", "@astrojs/preact": "4.1.1", "@astrojs/rss": "4.0.12", + "@astrojs/sitemap": "^3.6.0", "@astrojs/vercel": "^8.2.8", "@glidejs/glide": "^3.7.1", "@nanostores/persistent": "^1.1.0", @@ -25,7 +26,7 @@ "@types/hast": "^3.0.4", "@vite-pwa/astro": "^1.1.0", "ansi-colors": "4.1.3", - "astro": "5.14.5", + "astro": "5.14.8", "astro-breadcrumbs": "3.3.1", "astro-icon": "^1.1.5", "canvas": "^3.2.0", @@ -61,8 +62,8 @@ }, "devDependencies": { "@babel/core": "^7.28.4", - "@eslint/js": "9.37.0", - "@playwright/test": "1.56.0", + "@eslint/js": "9.38.0", + "@playwright/test": "1.56.1", "@testing-library/dom": "10.4.1", "@testing-library/preact": "3.2.4", "@testing-library/user-event": "14.6.1", @@ -82,16 +83,16 @@ "@types/to-ico": "1.1.3", "@types/yargs": "17.0.33", "@typescript-eslint/eslint-plugin": "8.46.1", - "@typescript-eslint/parser": "8.46.1", - "@vitest/coverage-v8": "^3.2.4", + "@typescript-eslint/parser": "8.46.2", + "@vitest/coverage-v8": "^4.0.0", "confusing-browser-globals": "1.0.11", "cross-spawn": "7.0.6", "dedent": "^1.7.0", - "eslint": "9.37.0", + "eslint": "9.38.0", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-astro": "1.3.1", "eslint-plugin-import": "2.32.0", - "eslint-plugin-jsdoc": "61.1.2", + "eslint-plugin-jsdoc": "61.1.5", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-security": "3.0.1", "eslint-plugin-yml": "1.19.0", @@ -120,10 +121,10 @@ "temp-dir": "3.0.0", "tslib": "2.8.1", "typescript": "5.9.3", - "typescript-eslint": "8.46.1", + "typescript-eslint": "8.46.2", "unist-util-inspect": "^8.1.0", "unist-util-visit": "^5.0.0", - "vitest": "3.2.4", + "vitest": "4.0.0", "vitest-axe": "0.1.0" }, "engines": { @@ -134,20 +135,6 @@ "@rollup/rollup-linux-x64-gnu": "^4.40.1" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", @@ -246,9 +233,9 @@ "license": "MIT" }, "node_modules/@astrojs/check": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.4.tgz", - "integrity": "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==", + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.5.tgz", + "integrity": "sha512-88vc8n2eJ1Oua74yXSGo/8ABMeypfQPGEzuoAx2awL9Ju8cE6tZ2Rz9jVx5hIExHK5gKVhpxfZj4WXm7e32g1w==", "license": "MIT", "dependencies": { "@astrojs/language-server": "^2.15.0", @@ -597,6 +584,26 @@ "kleur": "^4.1.5" } }, + "node_modules/@astrojs/sitemap": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.6.0.tgz", + "integrity": "sha512-4aHkvcOZBWJigRmMIAJwRQXBS+ayoP5z40OklTXYXhUDhwusz+DyDl+nSshY6y9DvkVEavwNcFO8FD81iGhXjg==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/sitemap/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@astrojs/telemetry": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", @@ -3011,13 +3018,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -3026,9 +3033,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", - "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", + "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3113,9 +3120,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", - "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", "dev": true, "license": "MIT", "engines": { @@ -3126,9 +3133,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4057,16 +4064,6 @@ "node": ">=18.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -4799,13 +4796,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", - "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.0" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -6289,6 +6286,19 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@sindresorhus/base62": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", + "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -6307,6 +6317,13 @@ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -6777,13 +6794,14 @@ "license": "MIT" }, "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, "node_modules/@types/confusing-browser-globals": { @@ -7035,6 +7053,15 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", @@ -7208,17 +7235,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz", - "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", + "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.46.1", - "@typescript-eslint/types": "8.46.1", - "@typescript-eslint/typescript-estree": "8.46.1", - "@typescript-eslint/visitor-keys": "8.46.1", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4" }, "engines": { @@ -7233,6 +7260,140 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/project-service": { "version": "8.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", @@ -7948,32 +8109,30 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.0.tgz", + "integrity": "sha512-VyVmZ8TccaGCZT9C0/vIi3kbPmvD/rLUcRx1AC8QzqaCm9SLYr1p3rxirW3EuwEGWnVU7KjGckAwJR1SRtgurA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", + "@vitest/utils": "4.0.0", + "ast-v8-to-istanbul": "^0.3.5", + "debug": "^4.4.3", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", + "istanbul-reports": "^3.2.0", "magicast": "^0.3.5", "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "@vitest/browser": "4.0.0", + "vitest": "4.0.0" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -7982,39 +8141,40 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.0.tgz", + "integrity": "sha512-NLwsOv2m6RfTEMk5AhpFIUVbd5BDmZnev5XxIIwJiNsXFOetFdqMzil/paGpwwbfQyaeQCokB1rQbKsnvLeR/w==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.0.0", + "@vitest/utils": "4.0.0", + "chai": "^6.0.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.0.tgz", + "integrity": "sha512-s5S729mda0Umb60zbZeyYm58dpv97VNOXZ1bLSZ9AfaOE8TJoW4IDfEnw3IaCk9nq/Hug80hFmAz5NAh+XOImQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.0.0", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.19" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -8026,42 +8186,41 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.0.tgz", + "integrity": "sha512-oUjxwO6VcUP0VtCkJERILS2yKV4AZiE1VTWDjvjeb8pXG6P5iubyeP+cmcj2vzCPdUst8vNhXMqC1CnxvDN97Q==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.0.tgz", + "integrity": "sha512-w3kADT0nDmY4dQyfPtq7zEe6wbwDy88Go2b7NpWuj0iqA1H26CTS/JB2/t8tKbvxk7MTJ9vTsRK/VMVuKmLPaQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.0", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.0.tgz", + "integrity": "sha512-ELrK8qhbH3WdhD/2qh3NnR7xnaxOGx62NYLj5XKAGPIABOc+1ITN1XfH/MTgdP6Ov7O91DycuGrzwpizdCpuHg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.0.0", + "magic-string": "^0.30.19", "pathe": "^2.0.3" }, "funding": { @@ -8069,28 +8228,24 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.0.tgz", + "integrity": "sha512-VKD9p74W9ALFV2dSy3j8WtitY3gtloO+U6EZq84TY5gTaTTt1Lvs9nZnuaBomzEHYp/QbtGRMMKBOCsir2IAgA==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.0.tgz", + "integrity": "sha512-8OXfn18Y7UtcpqhiQAfxcmZIsemPPKcg/guU2CLvafXn50G6B2EvKuj3A5h7ZMAvm95tDkll13rTtdd08MKjLQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.0", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -8587,9 +8742,9 @@ } }, "node_modules/astro": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.14.5.tgz", - "integrity": "sha512-EHt7y3+nHYyKzBats1AL3N4Pyrvqyr+zXBC7njUa9Tfe+gsiHlunaw+lXitTT/DDVwO2R/f/qVG7Xc6rl0b2KQ==", + "version": "5.14.8", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.14.8.tgz", + "integrity": "sha512-nKqCLs7BFvGQL9QWQOUqxHhlHtV0UMLXz1ANJygozvjcexBWS7FYkWI2LzRPMNYmbW4msIWNWnX2RvLdvI5Cnw==", "license": "MIT", "peer": true, "dependencies": { @@ -9483,16 +9638,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cacheable": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.1.0.tgz", @@ -9648,18 +9793,11 @@ } }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -9729,16 +9867,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/cheerio-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", @@ -10474,16 +10602,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -11247,26 +11365,25 @@ } }, "node_modules/eslint": { - "version": "9.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", - "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.4.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.37.0", + "@eslint/js": "9.38.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -11529,9 +11646,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "61.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.1.2.tgz", - "integrity": "sha512-gbb4VVKRsMJZ+YqJXte6RQ/pDTIQcMauyQb03n5GTfWG+gAb6T3FaOLeoSHAIrBT90ykLkxzRh3z95DoIrUk3A==", + "version": "61.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.1.5.tgz", + "integrity": "sha512-UZ+7M6WVFBVRTxHZURxYP7M++M+ZEjxPGB/CScdrKAhzpf/LWS1HaNRHMOkISkOTTggMhwRwgKmVlTLQryXV2Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11543,10 +11660,11 @@ "espree": "^10.4.0", "esquery": "^1.6.0", "html-entities": "^2.6.0", - "object-deep-merge": "^1.0.5", + "object-deep-merge": "^2.0.0", "parse-imports-exports": "^0.2.4", "semver": "^7.7.3", - "spdx-expression-parse": "^4.0.0" + "spdx-expression-parse": "^4.0.0", + "to-valid-identifier": "^1.0.0" }, "engines": { "node": ">=20.11.0" @@ -11887,9 +12005,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -14988,13 +15106,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -16680,27 +16791,11 @@ } }, "node_modules/object-deep-merge": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-1.0.5.tgz", - "integrity": "sha512-3DioFgOzetbxbeUq8pB2NunXo8V0n4EvqsWM/cJoI6IA9zghd7cl/2pBOuWRf4dlvA+fcg5ugFMZaN2/RuoaGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "4.2.0" - } - }, - "node_modules/object-deep-merge/node_modules/type-fest": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.2.0.tgz", - "integrity": "sha512-5zknd7Dss75pMSED270A1RQS3KloqRJA9XbXLe0eCxyw7xXFb3rd+9B0UQ/0E+LQT6lnrLviEolYORlRWamn4w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", + "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/object-inspect": { "version": "1.13.4", @@ -17269,16 +17364,6 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -17397,13 +17482,13 @@ } }, "node_modules/playwright": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", - "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.0" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -17416,9 +17501,9 @@ } }, "node_modules/playwright-core": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", - "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -18672,6 +18757,19 @@ } } }, + "node_modules/reserved-identifiers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.0.0.tgz", + "integrity": "sha512-h0bP2Katmvf3hv4Z3WtDl4+6xt/OglQ2Xa6TnhZ/Rm9/7IH1crXQqMwD4J2ngKBonVv+fB55zfGgNDAmsevLVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resize-img": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/resize-img/-/resize-img-1.1.2.tgz", @@ -19356,22 +19454,53 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "node_modules/sitemap": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.1.tgz", + "integrity": "sha512-4Y8ynSMFAy/DadeAeio8Kx4zfC8/0VcKi7TH0I1SazvBcrU2fpJaGoeWsX1FMRaHoe3VGMA53DqVoLErZrtG9Q==", "license": "MIT", "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" }, "engines": { - "node": ">=8" + "node": ">=14.0.0", + "npm": ">=6.0.0" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/sitemap/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { @@ -19569,6 +19698,12 @@ "node": ">= 0.4" } }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, "node_modules/stream-to": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz", @@ -19824,19 +19959,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/strnum": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", @@ -20694,98 +20816,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/test-exclude/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -20834,30 +20864,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -20918,6 +20928,23 @@ "node": ">=8.0" } }, + "node_modules/to-valid-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", + "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/base62": "^1.0.0", + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -21228,16 +21255,195 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.1.tgz", - "integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz", + "integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.1", - "@typescript-eslint/parser": "8.46.1", - "@typescript-eslint/typescript-estree": "8.46.1", - "@typescript-eslint/utils": "8.46.1" + "@typescript-eslint/eslint-plugin": "8.46.2", + "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", + "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.2", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", + "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -21251,6 +21457,50 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/typescript-eslint/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", @@ -22014,29 +22264,6 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vite-plugin-pwa": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.0.3.tgz", @@ -22180,42 +22407,39 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.0.tgz", + "integrity": "sha512-Z+qKuTt2py+trSv2eJNYPaQKos88EmmLntXLAJkOHdd1v3BdcS4DgIkyC6cQPRoh8tWb+QiFfW08U347mjcV0g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.0", + "@vitest/mocker": "4.0.0", + "@vitest/pretty-format": "4.0.0", + "@vitest/runner": "4.0.0", + "@vitest/snapshot": "4.0.0", + "@vitest/spy": "4.0.0", + "@vitest/utils": "4.0.0", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.19", "pathe": "^2.0.3", - "picomatch": "^4.0.2", + "picomatch": "^4.0.3", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -22223,9 +22447,11 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.0", + "@vitest/browser-preview": "4.0.0", + "@vitest/browser-webdriverio": "4.0.0", + "@vitest/ui": "4.0.0", "happy-dom": "*", "jsdom": "*" }, @@ -22239,7 +22465,13 @@ "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { diff --git a/package.json b/package.json index 7df49cab3..91a5e4891 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@astrojs/mdx": "4.3.7", "@astrojs/preact": "4.1.1", "@astrojs/rss": "4.0.12", + "@astrojs/sitemap": "^3.6.0", "@astrojs/vercel": "^8.2.8", "@glidejs/glide": "^3.7.1", "@nanostores/persistent": "^1.1.0", @@ -135,7 +136,7 @@ "@types/svg-sprite": "0.0.39", "@types/to-ico": "1.1.3", "@types/yargs": "17.0.33", - "@typescript-eslint/eslint-plugin": "8.46.2", + "@typescript-eslint/eslint-plugin": "8.46.1", "@typescript-eslint/parser": "8.46.2", "@vitest/coverage-v8": "^4.0.0", "confusing-browser-globals": "1.0.11", diff --git a/src/components/Cookies/Consent/client.ts b/src/components/Cookies/Consent/client.ts index b8985ffb8..e4a201c2c 100644 --- a/src/components/Cookies/Consent/client.ts +++ b/src/components/Cookies/Consent/client.ts @@ -24,7 +24,7 @@ import { handleScriptError, addScriptBreadcrumb } from '@components/Scripts/erro */ export class CookieConsent extends LoadableScript { static override scriptName = 'CookieConsent' - static override eventType: TriggerEvent = 'delayed' + static override eventType: TriggerEvent = 'astro:page-load' /** Modal wrapper element */ wrapper: HTMLDivElement diff --git a/src/components/Head/Meta.astro b/src/components/Head/Meta.astro index afc184fec..56b6aef83 100644 --- a/src/components/Head/Meta.astro +++ b/src/components/Head/Meta.astro @@ -82,3 +82,5 @@ const slug = path.replace(/^\//, '').replace(/\/$/, '') || 'home' +{/* Sitemap */} + \ No newline at end of file diff --git a/src/components/Scripts/state/index.ts b/src/components/Scripts/state/index.ts index a4d50ac03..a6481ac9d 100644 --- a/src/components/Scripts/state/index.ts +++ b/src/components/Scripts/state/index.ts @@ -347,7 +347,7 @@ export function initStateSideEffects(): void { // Side Effect 6: Show/hide cookie modal $cookieModalVisible.subscribe(visible => { - const modal = document.getElementById('cookie-consent-modal-id') + const modal = document.getElementById('cookie-modal-id') if (modal) { modal.style.display = visible ? 'flex' : 'none' } diff --git a/src/lib/config/sitemap-serialize.ts b/src/lib/config/sitemap-serialize.ts new file mode 100644 index 000000000..169830920 --- /dev/null +++ b/src/lib/config/sitemap-serialize.ts @@ -0,0 +1,79 @@ +import { writeFileSync, mkdirSync } from 'fs' +import { join } from 'path' +import type { SitemapItem } from '@astrojs/sitemap' + +// Accumulator for pages data +const pagesData: Record = {} + +/** + * Serialize function for sitemap that also collects page data + */ +export function serializeSitemapItem(item: SitemapItem): SitemapItem | undefined { + const urlObject = new URL(item.url) + const pathParts = urlObject.pathname.split('/').filter(Boolean) + const topLevelPath = pathParts[0] ?? '' + + // Collect page data + if (topLevelPath) { + if (pathParts.length === 1) { + // Single-level page (e.g., /privacy) + // Only mark as true if it doesn't already have an array of sub-pages + if (!pagesData[topLevelPath]) { + pagesData[topLevelPath] = true + } + } else { + // Multi-level page (e.g., /articles/my-post) + const slug = pathParts.slice(1).join('/') + + // Convert to array if it was marked as true (index page) + if (pagesData[topLevelPath] === true) { + pagesData[topLevelPath] = [] + } else if (!pagesData[topLevelPath]) { + pagesData[topLevelPath] = [] + } + + // Add slug if it's an array + if (Array.isArray(pagesData[topLevelPath])) { + if (!pagesData[topLevelPath].includes(slug)) { + pagesData[topLevelPath].push(slug) + } + } + } + } + + // Skip excluded paths from sitemap + if (topLevelPath === 'downloads' || topLevelPath === 'social-shares') { + return undefined + } + + return item +} + +/** + * Write the collected pages data to .cache/pages.json + */ +export function writePagesJson(): void { + const cacheDir = join(process.cwd(), '.cache') + const pagesFile = join(cacheDir, 'pages.json') + + try { + mkdirSync(cacheDir, { recursive: true }) + + // Transform data into array format + const transformedData: (string | Record)[] = [] + for (const [key, value] of Object.entries(pagesData)) { + if (value === true) { + // Single-level page: add as string + transformedData.push(key) + } else { + // Multi-level page: add as object + transformedData.push({ [key]: value }) + } + } + + writeFileSync(pagesFile, JSON.stringify(transformedData, null, 2), 'utf-8') + console.log(`āœ… Pages data written to ${pagesFile}`) + } catch (error) { + console.error('āŒ Failed to write pages.json:', error) + } +} \ No newline at end of file diff --git a/src/pages/robots.txt.ts b/src/pages/robots.txt.ts new file mode 100644 index 000000000..147941f18 --- /dev/null +++ b/src/pages/robots.txt.ts @@ -0,0 +1,13 @@ +import type { APIRoute } from 'astro' + +const getRobotsTxt = (sitemapURL: URL) => `\ +User-agent: * +Allow: / + +Sitemap: ${sitemapURL.href} +` + +export const GET: APIRoute = ({ site }) => { + const sitemapURL = new URL('sitemap-index.xml', site) + return new Response(getRobotsTxt(sitemapURL)) +} diff --git a/test/e2e/helpers/__fixtures__/pages.json b/test/e2e/helpers/__fixtures__/pages.json new file mode 100644 index 000000000..6071c23cb --- /dev/null +++ b/test/e2e/helpers/__fixtures__/pages.json @@ -0,0 +1,58 @@ +[ + "about", + { + "articles": [ + "test-article", + "typescript-best-practices", + "useful-vs-code-extensions", + "writing-library-code" + ] + }, + { + "case-studies": [ + "division-15", + "ecommerce-modernization", + "english-first", + "enterprise-api-platform", + "labcorp", + "us-logistics" + ] + }, + "contact", + "cookies", + { + "downloads": [ + "api-tool-consolidation-whitepaper", + "identity-security-for-dummies", + "lakehouse-analytics-guide", + "observability-benefits-guide", + "ransomware-recovery-kit" + ] + }, + "offline", + "privacy", + { + "services": [ + "create-custom-font-sets", + "overview" + ] + }, + { + "social-shares": [ + "template" + ] + }, + { + "tags": [ + "apiDesign", + "cms", + "code", + "crm", + "graphql", + "online-learning", + "react", + "services", + "typescript" + ] + } +] \ No newline at end of file diff --git a/test/e2e/helpers/__tests__/baseTest.spec.ts b/test/e2e/helpers/__tests__/baseTest.spec.ts new file mode 100644 index 000000000..5bafe0420 --- /dev/null +++ b/test/e2e/helpers/__tests__/baseTest.spec.ts @@ -0,0 +1,157 @@ +import { describe, expect, it, vi } from 'vitest' +import fixtureData from '@test/e2e/helpers/__fixtures__/pages.json' + +// Mock the pages.json import +vi.mock('.cache/pages.json', () => ({ + default: fixtureData, +})) + +// Import after mocking +const { sitePaths } = await import('../baseTest') + +describe('baseTest helpers', () => { + describe('sitePaths.articles', () => { + it('should extract article paths', () => { + expect(sitePaths.articles).toMatchInlineSnapshot(` + [ + "/articles/test-article", + "/articles/typescript-best-practices", + "/articles/useful-vs-code-extensions", + "/articles/writing-library-code", + ] + `) + }) + }) + + describe('sitePaths.caseStudies', () => { + it('should extract case-studies paths', () => { + expect(sitePaths.caseStudies).toMatchInlineSnapshot(` + [ + "/case-studies/division-15", + "/case-studies/ecommerce-modernization", + "/case-studies/english-first", + "/case-studies/enterprise-api-platform", + "/case-studies/labcorp", + "/case-studies/us-logistics", + ] + `) + }) + }) + + describe('sitePaths.services', () => { + it('should extract services paths', () => { + expect(sitePaths.services).toMatchInlineSnapshot(` + [ + "/services/create-custom-font-sets", + "/services/overview", + ] + `) + }) + }) + + describe('sitePaths.tags', () => { + it('should extract tags paths', () => { + expect(sitePaths.tags).toMatchInlineSnapshot(` + [ + "/tags/apiDesign", + "/tags/cms", + "/tags/code", + "/tags/crm", + "/tags/graphql", + "/tags/online-learning", + "/tags/react", + "/tags/services", + "/tags/typescript", + ] + `) + }) + }) + + describe('sitePaths.downloads', () => { + it('should extract downloads paths', () => { + expect(sitePaths.downloads).toMatchInlineSnapshot(` + [ + "/downloads/api-tool-consolidation-whitepaper", + "/downloads/identity-security-for-dummies", + "/downloads/lakehouse-analytics-guide", + "/downloads/observability-benefits-guide", + "/downloads/ransomware-recovery-kit", + ] + `) + }) + }) + + describe('sitePaths.socialShares', () => { + it('should extract social-shares paths', () => { + expect(sitePaths.socialShares).toMatchInlineSnapshot(` + [ + "/social-shares/template", + ] + `) + }) + }) + + it('should extract all single-page paths', () => { + expect(sitePaths.singlePages).toMatchInlineSnapshot(` + [ + "/about", + "/articles", + "/case-studies", + "/contact", + "/cookies", + "/downloads", + "/offline", + "/privacy", + "/services", + "/social-shares", + "/tags", + ] + `) + }) + + it('should extract all pages', () => { + expect(sitePaths.allPages).toMatchInlineSnapshot(` + [ + "/articles/test-article", + "/articles/typescript-best-practices", + "/articles/useful-vs-code-extensions", + "/articles/writing-library-code", + "/case-studies/division-15", + "/case-studies/ecommerce-modernization", + "/case-studies/english-first", + "/case-studies/enterprise-api-platform", + "/case-studies/labcorp", + "/case-studies/us-logistics", + "/services/create-custom-font-sets", + "/services/overview", + "/tags/apiDesign", + "/tags/cms", + "/tags/code", + "/tags/crm", + "/tags/graphql", + "/tags/online-learning", + "/tags/react", + "/tags/services", + "/tags/typescript", + "/downloads/api-tool-consolidation-whitepaper", + "/downloads/identity-security-for-dummies", + "/downloads/lakehouse-analytics-guide", + "/downloads/observability-benefits-guide", + "/downloads/ransomware-recovery-kit", + "/social-shares/template", + "/about", + "/articles", + "/case-studies", + "/contact", + "/cookies", + "/downloads", + "/offline", + "/privacy", + "/services", + "/social-shares", + "/tags", + "/non-existent-page", + ] + `) + }) +}) diff --git a/test/e2e/helpers/baseTest.ts b/test/e2e/helpers/baseTest.ts new file mode 100644 index 000000000..8cb64bd14 --- /dev/null +++ b/test/e2e/helpers/baseTest.ts @@ -0,0 +1,144 @@ +/** + * Extended Playwright test with site pages data + * + * Usage example: + * ```typescript + * import { test, expect } from '../helpers/baseTest' + * + * test('navigate to first article', async ({ page, articlePaths }) => { + * await page.goto(articlePaths[0]) + * await expect(page).toHaveURL(/\/articles\//) + * }) + * + * test('check all tag pages exist', async ({ page, tagPaths }) => { + * for (const tagPath of tagPaths) { + * const response = await page.goto(tagPath) + * expect(response?.status()).toBe(200) + * } + * }) + * + * test('navigate to specific case study', async ({ page, caseStudyPaths }) => { + * const enterprisePath = caseStudyPaths.find(p => p.includes('enterprise')) + * await page.goto(enterprisePath!) + * }) + * ``` + */ +import { test as baseTest, expect } from '@playwright/test' +import pagesData from '../../../.cache/pages.json' with { type: 'json' } + +/** + * Extract and normalize paths for a specific key from pages.json + */ +function extractPaths(key: string): string[] { + for (const item of pagesData) { + if (typeof item === 'object' && key in item) { + const slugs = (item as Record)[key] + if (!slugs) return [] + return slugs.map((slug: string) => `/${key}/${slug}`) + } + } + return [] +} + +/** + * Extract all single-page paths from pages.json + * Includes both standalone pages (about, contact) and list view pages for collections (articles, tags) + */ +function extractSinglePages(): string[] { + const pages: string[] = [] + + for (const item of pagesData) { + if (typeof item === 'string') { + // Standalone single pages like /about, /contact + pages.push(`/${item}`) + } else { + // List view pages for collections like /articles, /tags + for (const key of Object.keys(item)) { + pages.push(`/${key}`) + } + } + } + + return pages +} + +// Pre-computed path arrays +export const _sitePaths = { + articles: extractPaths('articles'), + caseStudies: extractPaths('case-studies'), + services: extractPaths('services'), + tags: extractPaths('tags'), + downloads: extractPaths('downloads'), + socialShares: extractPaths('social-shares'), + singlePages: extractSinglePages(), +} + +export const sitePaths = { + articles: extractPaths('articles'), + caseStudies: extractPaths('case-studies'), + services: extractPaths('services'), + tags: extractPaths('tags'), + downloads: extractPaths('downloads'), + socialShares: extractPaths('social-shares'), + singlePages: extractSinglePages(), + allPages: [ + ..._sitePaths.articles, + ..._sitePaths.caseStudies, + ..._sitePaths.services, + ..._sitePaths.tags, + ..._sitePaths.downloads, + ..._sitePaths.socialShares, + ..._sitePaths.singlePages, + '/non-existent-page', + ], +} + +/** + * Extended test method with content collections available as context + */ +export const test = baseTest.extend<{ + articlePaths: string[] + caseStudyPaths: string[] + servicePaths: string[] + tagPaths: string[] + downloadPaths: string[] + socialSharePaths: string[] + singlePagePaths: string[] + allPaths: string[] +}>({ + articlePaths: async ({}, use) => { + await use(sitePaths.articles) + }, + caseStudyPaths: async ({}, use) => { + await use(sitePaths.caseStudies) + }, + servicePaths: async ({}, use) => { + await use(sitePaths.services) + }, + tagPaths: async ({}, use) => { + await use(sitePaths.tags) + }, + downloadPaths: async ({}, use) => { + await use(sitePaths.downloads) + }, + socialSharePaths: async ({}, use) => { + await use(sitePaths.socialShares) + }, + singlePagePaths: async ({}, use) => { + await use(sitePaths.singlePages) + }, + allPaths: async ({}, use) => { + const all = [ + ...sitePaths.articles, + ...sitePaths.caseStudies, + ...sitePaths.services, + ...sitePaths.tags, + ...sitePaths.downloads, + ...sitePaths.socialShares, + ...sitePaths.singlePages, + ] + await use(all) + }, +}) + +export { expect } diff --git a/test/e2e/helpers/browser-state.ts b/test/e2e/helpers/browserState.ts similarity index 100% rename from test/e2e/helpers/browser-state.ts rename to test/e2e/helpers/browserState.ts diff --git a/test/e2e/helpers/console-errors.ts b/test/e2e/helpers/consoleErrors.ts similarity index 96% rename from test/e2e/helpers/console-errors.ts rename to test/e2e/helpers/consoleErrors.ts index 7dac551a8..137ee42f7 100644 --- a/test/e2e/helpers/console-errors.ts +++ b/test/e2e/helpers/consoleErrors.ts @@ -66,6 +66,9 @@ export function setupConsoleErrorChecker(page: Page): ConsoleErrorChecker { // Filter known browser quirks if (error.includes('ResizeObserver loop completed')) return false + // Filter Vite dev server 504 errors (Outdated Optimize Dep) + if (error.includes('504') && error.includes('Outdated Optimize Dep')) return false + // Filter generic "Failed to load resource: 404" errors if we have acceptable 404s // These console errors don't include the URL, so if we filtered out 404s, // we should also filter out the corresponding console errors diff --git a/test/e2e/helpers/index.ts b/test/e2e/helpers/index.ts new file mode 100644 index 000000000..a2a015b4e --- /dev/null +++ b/test/e2e/helpers/index.ts @@ -0,0 +1,3 @@ +export { test, expect } from './baseTest' +export { setupConsoleErrorChecker, logConsoleErrors } from './consoleErrors' +export { clearConsentCookies } from './browserState' diff --git a/test/e2e/specs/01-smoke/critical-paths.spec.ts b/test/e2e/specs/01-smoke/critical-paths.spec.ts index a0bd968fa..641583142 100644 --- a/test/e2e/specs/01-smoke/critical-paths.spec.ts +++ b/test/e2e/specs/01-smoke/critical-paths.spec.ts @@ -3,20 +3,23 @@ * These tests verify the most essential functionality of the site. * They should always pass and run quickly. */ -import { test, expect } from '@playwright/test' -import { TEST_URLS } from '@test/e2e/fixtures/test-data' -import { setupConsoleErrorChecker, logConsoleErrors } from '@test/e2e/helpers/console-errors' -import { clearConsentCookies } from '@test/e2e/helpers/browser-state' +import { + test, + expect, + setupConsoleErrorChecker, + logConsoleErrors, + clearConsentCookies, +} from '@test/e2e/helpers' test.describe('Critical Paths @smoke', () => { test('@ready all main pages are accessible', async ({ page }) => { const mainPages = [ - { path: TEST_URLS.home, title: /Webstack Builders/ }, - { path: TEST_URLS.about, title: /About/ }, - { path: TEST_URLS.articles, title: /Articles/ }, - { path: TEST_URLS.services, title: /Services/ }, - { path: TEST_URLS.caseStudies, title: /Case Studies/ }, - { path: TEST_URLS.contact, title: /Contact/ }, + { path: '/', title: /Webstack Builders/ }, + { path: '/about', title: /About/ }, + { path: '/articles', title: /Articles/ }, + { path: '/services', title: /Services/ }, + { path: '/case-studies', title: /Case Studies/ }, + { path: '/contact', title: /Contact/ }, ] for (const { path, title } of mainPages) { @@ -31,7 +34,7 @@ test.describe('Critical Paths @smoke', () => { // Expected: Can navigate between all main pages via nav menu // Actual: Unknown - needs testing - await page.goto(TEST_URLS.home) + await page.goto('/') // Click About link await page.click('a[href="/about"]') @@ -51,7 +54,7 @@ test.describe('Critical Paths @smoke', () => { }) test('@ready footer is present on all pages', async ({ page }) => { - const pages = [TEST_URLS.home, TEST_URLS.about, TEST_URLS.contact] + const pages = ['/', '/about', '/contact'] for (const path of pages) { await page.goto(path) @@ -63,7 +66,7 @@ test.describe('Critical Paths @smoke', () => { // Expected: Contact form should be visible with all required fields // Actual: Unknown - needs testing - await page.goto(TEST_URLS.contact) + await page.goto('/contact') await expect(page.locator('#contactForm')).toBeVisible() await expect(page.locator('#name')).toBeVisible() await expect(page.locator('#email')).toBeVisible() @@ -74,14 +77,14 @@ test.describe('Critical Paths @smoke', () => { // Expected: Newsletter form should be visible on homepage // Actual: Unknown - needs testing - await page.goto(TEST_URLS.home) + await page.goto('/') await expect(page.locator('#newsletter-form')).toBeVisible() await expect(page.locator('#newsletter-email')).toBeVisible() await expect(page.locator('#newsletter-gdpr-consent')).toBeVisible() }) test('@ready 404 page displays for invalid routes', async ({ page }) => { - await page.goto(TEST_URLS.notFound) + await page.goto('/this-page-does-not-exist') // Should show 404 content await expect(page.locator('h1')).toContainText(/404|Not Found/i) @@ -91,7 +94,7 @@ test.describe('Critical Paths @smoke', () => { // Expected: Theme picker button should be visible and clickable // Actual: Unknown - needs testing - await page.goto(TEST_URLS.home) + await page.goto('/') await expect(page.locator('button[aria-label="toggle theme switcher"]')).toBeVisible() }) @@ -99,7 +102,7 @@ test.describe('Critical Paths @smoke', () => { // Clear consent cookies to force banner to appear await clearConsentCookies(context) - await page.goto(TEST_URLS.home) + await page.goto('/') await page.waitForLoadState('networkidle') // Cookie modal should be visible @@ -107,14 +110,7 @@ test.describe('Critical Paths @smoke', () => { }) test('@ready main pages have no 404 errors', async ({ page }) => { - const mainPages = [ - TEST_URLS.home, - TEST_URLS.about, - TEST_URLS.articles, - TEST_URLS.services, - TEST_URLS.caseStudies, - TEST_URLS.contact, - ] + const mainPages = ['/', '/about', '/articles', '/services', '/case-studies', '/contact'] for (const path of mainPages) { const errorChecker = setupConsoleErrorChecker(page) @@ -128,7 +124,7 @@ test.describe('Critical Paths @smoke', () => { }) test('@ready main pages have no console errors', async ({ page }) => { - const mainPages = [TEST_URLS.home, TEST_URLS.about, TEST_URLS.articles, TEST_URLS.contact] + const mainPages = ['/', '/about', '/articles', '/contact'] for (const path of mainPages) { const errorChecker = setupConsoleErrorChecker(page) diff --git a/test/e2e/specs/01-smoke/site.spec.ts b/test/e2e/specs/01-smoke/site.spec.ts new file mode 100644 index 000000000..7cd59882a --- /dev/null +++ b/test/e2e/specs/01-smoke/site.spec.ts @@ -0,0 +1,50 @@ +/** + * Site-wide Smoke Tests + * Tests for site-level functionality (RSS, manifest, 404 pages) + */ +import { test, expect } from '@playwright/test' +import { TEST_URLS } from '@test/e2e/fixtures/test-data' + +test.describe('Site-wide Features @smoke', () => { + test('@ready 404 page displays for invalid routes', async ({ page }) => { + await page.goto(TEST_URLS.notFound) + + // Should show 404 content + await expect(page.locator('h1')).toContainText(/404|Not Found/i) + }) + + test('@ready RSS feed is accessible', async ({ page }) => { + // RSS feed should be accessible and valid XML + const response = await page.goto('/rss.xml') + expect(response?.status()).toBe(200) + + const contentType = response?.headers()['content-type'] + expect(contentType).toMatch(/xml/) + + // Get raw response text, not browser-rendered HTML + const content = await response!.text() + expect(content).toContain(' { + // PWA manifest should be accessible and valid JSON + // Use request API instead of page.goto to avoid download issues in Firefox + const response = await request.get('/manifest.json') + expect(response.status()).toBe(200) + + const contentType = response.headers()['content-type'] + expect(contentType).toMatch(/json/) + + // Get response content + const content = await response.text() + const manifest = JSON.parse(content) + + // Verify required manifest fields + expect(manifest.name).toBeTruthy() + expect(manifest.short_name).toBeTruthy() + expect(manifest.start_url).toBeTruthy() + expect(manifest.icons).toBeTruthy() + expect(Array.isArray(manifest.icons)).toBe(true) + }) +}) diff --git a/test/e2e/specs/02-pages/article-detail.spec.ts b/test/e2e/specs/02-pages/article-detail.spec.ts index 3220db03d..75131a2b1 100644 --- a/test/e2e/specs/02-pages/article-detail.spec.ts +++ b/test/e2e/specs/02-pages/article-detail.spec.ts @@ -4,7 +4,7 @@ * Note: Tests are dynamically generated for each article */ import { test, expect } from '@playwright/test' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' import { fetchArticles } from '@test/e2e/helpers/content-fetchers' // Shared article data diff --git a/test/e2e/specs/02-pages/articles.spec.ts b/test/e2e/specs/02-pages/articles.spec.ts index 4ff50ad3c..f0ac54285 100644 --- a/test/e2e/specs/02-pages/articles.spec.ts +++ b/test/e2e/specs/02-pages/articles.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test' import { TEST_URLS } from '@test/e2e/fixtures/test-data' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' test.describe('Articles Page', () => { test.beforeEach(async ({ page }) => { diff --git a/test/e2e/specs/02-pages/case-studies.spec.ts b/test/e2e/specs/02-pages/case-studies.spec.ts index afe89348f..560ae36f0 100644 --- a/test/e2e/specs/02-pages/case-studies.spec.ts +++ b/test/e2e/specs/02-pages/case-studies.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test' import { TEST_URLS } from '../../fixtures/test-data' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' test.describe('Case Studies List Page', () => { test.beforeEach(async ({ page }) => { diff --git a/test/e2e/specs/02-pages/case-study-detail.spec.ts b/test/e2e/specs/02-pages/case-study-detail.spec.ts index 68351929c..b02694a0f 100644 --- a/test/e2e/specs/02-pages/case-study-detail.spec.ts +++ b/test/e2e/specs/02-pages/case-study-detail.spec.ts @@ -4,7 +4,7 @@ * Note: Tests are generated dynamically per case study */ import { test, expect } from '@playwright/test' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' /** * Generate test suite for a specific case study diff --git a/test/e2e/specs/02-pages/contact.spec.ts b/test/e2e/specs/02-pages/contact.spec.ts index 8e1f92870..e46b91bcf 100644 --- a/test/e2e/specs/02-pages/contact.spec.ts +++ b/test/e2e/specs/02-pages/contact.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test' import { TEST_URLS } from '@test/e2e/fixtures/test-data' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' test.describe('Contact Page', () => { test.beforeEach(async ({ page }) => { diff --git a/test/e2e/specs/02-pages/homepage.spec.ts b/test/e2e/specs/02-pages/homepage.spec.ts index 6e1281864..4aa4ed268 100644 --- a/test/e2e/specs/02-pages/homepage.spec.ts +++ b/test/e2e/specs/02-pages/homepage.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test' import { TEST_URLS } from '@test/e2e/fixtures/test-data' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' test.describe('Homepage', () => { test.beforeEach(async ({ page }) => { diff --git a/test/e2e/specs/02-pages/service-detail.spec.ts b/test/e2e/specs/02-pages/service-detail.spec.ts index 16e682187..862f3ef3c 100644 --- a/test/e2e/specs/02-pages/service-detail.spec.ts +++ b/test/e2e/specs/02-pages/service-detail.spec.ts @@ -4,7 +4,7 @@ * Note: Tests are dynamically generated for each service */ import { test, expect } from '@playwright/test' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' /** * Create a test suite for a specific service diff --git a/test/e2e/specs/02-pages/services.spec.ts b/test/e2e/specs/02-pages/services.spec.ts index 3d4c42f7b..ce5c2472a 100644 --- a/test/e2e/specs/02-pages/services.spec.ts +++ b/test/e2e/specs/02-pages/services.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test' import { TEST_URLS } from '@test/e2e/fixtures/test-data' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' test.describe('Services List Page', () => { test.beforeEach(async ({ page }) => { diff --git a/test/e2e/specs/02-pages/tags.spec.ts b/test/e2e/specs/02-pages/tags.spec.ts index 5fa2c6633..f53c1e359 100644 --- a/test/e2e/specs/02-pages/tags.spec.ts +++ b/test/e2e/specs/02-pages/tags.spec.ts @@ -3,7 +3,7 @@ * Tests for /tags index and individual tag pages */ import { test, expect } from '@playwright/test' -import { setupConsoleErrorChecker } from '@test/e2e/helpers/console-errors' +import { setupConsoleErrorChecker } from '@test/e2e/helpers/consoleErrors' test.describe('Tags Index Page', () => { test('@ready tags index page loads', async ({ page }) => { diff --git a/test/e2e/specs/debug-404s.spec.ts b/test/e2e/specs/debug-404s.spec.ts deleted file mode 100644 index f8bcc0219..000000000 --- a/test/e2e/specs/debug-404s.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Debug script to identify source of 404 errors - * - * DEBUG=true npx playwright test test/e2e/specs/debug-404s.spec.ts - */ -import { test } from '@playwright/test' - -test('debug 404 sources', async ({ page }) => { - const requests: any[] = [] - - // Capture all requests with full details - page.on('request', (request) => { - requests.push({ - url: request.url(), - method: request.method(), - resourceType: request.resourceType(), - initiator: request.frame().url(), - }) - }) - - page.on('response', (response) => { - if (response.status() === 404) { - const request = response.request() - console.log('\nšŸ”“ 404 ERROR DETAILS:') - console.log(' URL:', response.url()) - console.log(' Method:', request.method()) - console.log(' Resource Type:', request.resourceType()) - console.log(' Initiator Frame:', request.frame().url()) - console.log(' Headers:', JSON.stringify(request.headers(), null, 2)) - } - }) - - await page.goto('http://localhost:4321/') - await page.waitForLoadState('networkidle') - - // Wait a bit more to catch any delayed requests - await page.waitForTimeout(2000) - - console.log('\nāœ… Test complete') -}) diff --git a/vitest.config.ts b/vitest.config.ts index 159d36867..ee36809b0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -20,12 +20,20 @@ export default getViteConfig({ }, }, test: { - include: ['src/**/*.spec.ts?(x)', 'scripts/**/*.spec.ts', 'api/**/*.spec.ts'], + include: [ + 'src/**/*.spec.ts?(x)', + 'scripts/**/*.spec.ts', + 'api/**/*.spec.ts', + 'test/unit/**/*.spec.ts', + 'test/e2e/helpers/__tests__/**/*.spec.ts', + ], environmentMatchGlobs: [ ['src/**', 'jsdom'], ['src/lib/**', 'node'], ['scripts/**', 'node'], ['api/**', 'node'], + ['test/unit/**', 'node'], + ['test/e2e/helpers/__tests__/**', 'node'], ], testTimeout: 30 * 1000, setupFiles: ['./vitest.setup.ts'],