From 7194df03f4fd2996a550b1f720b033e7ba9e84ee Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 20:56:56 +0300
Subject: [PATCH 01/49] =?UTF-8?q?Fix=20flaky=20E2E=20test=20cases=20-=20sw?=
=?UTF-8?q?ap=20every=20self-import=20in=20that=20module=20to=20relative?=
=?UTF-8?q?=20./=E2=80=A6=20paths=20so=20the=20only=20alias=20use=20that?=
=?UTF-8?q?=20remains=20is=20from=20the=20spec=20files=20themselves,=20ext?=
=?UTF-8?q?end=20BasePage.waitForPageLoad()=20with=20a=20requireNext=20opt?=
=?UTF-8?q?ion=20so=20callers=20can=20specifically=20wait=20for=20the=20ne?=
=?UTF-8?q?xt=20astro:page-load=20event?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
_TODO.md | 5 ++++
test/e2e/helpers/index.ts | 24 +++++++++----------
test/e2e/helpers/pageObjectModels/BasePage.ts | 12 ++++++----
.../pageObjectModels/BreadCrumbPage.ts | 2 +-
.../e2e/specs/01-smoke/critical-paths.spec.ts | 4 ++--
5 files changed, 28 insertions(+), 19 deletions(-)
diff --git a/_TODO.md b/_TODO.md
index 677af9f25..e4e53b5c4 100644
--- a/_TODO.md
+++ b/_TODO.md
@@ -1,5 +1,10 @@
# TODO
+Next, I'd like to add a "pause" and "play" icon to src/components/Animations/Computers
+There are icons with those names already configured for the Icon component.
+There are hooks for pause and play already setup in the component.
+The icon should be displayed in the low right hand corner of the animation, with 4px of padding from the bottom and right side. It should overlay the animation, not expand the bounding box of the animation.
+
## Performance
Implement mitigations in test/e2e/specs/07-performance/PERFORMANCE.md
diff --git a/test/e2e/helpers/index.ts b/test/e2e/helpers/index.ts
index 115e9c804..662557e13 100644
--- a/test/e2e/helpers/index.ts
+++ b/test/e2e/helpers/index.ts
@@ -2,31 +2,31 @@ export {
test,
describe,
expect,
-} from '@test/e2e/helpers/baseTest'
+} from './baseTest'
export {
setupConsoleErrorChecker,
logConsoleErrors,
-} from '@test/e2e/helpers/consoleErrors'
+} from './consoleErrors'
export {
setupConsoleCapture,
printCapturedMessages,
-} from '@test/e2e/helpers/consoleCapture'
-export { clearConsentCookies } from '@test/e2e/helpers/browserState'
-export { BasePage } from '@test/e2e/helpers/pageObjectModels/BasePage'
-export { ComponentPersistencePage } from '@test/e2e/helpers/pageObjectModels/ComponentPersistencePage'
-export { HeadPage } from '@test/e2e/helpers/pageObjectModels/HeadPage'
-export { BreadCrumbPage } from '@test/e2e/helpers/pageObjectModels/BreadCrumbPage'
+} from './consoleCapture'
+export { clearConsentCookies } from './browserState'
+export { BasePage } from './pageObjectModels/BasePage'
+export { ComponentPersistencePage } from './pageObjectModels/ComponentPersistencePage'
+export { HeadPage } from './pageObjectModels/HeadPage'
+export { BreadCrumbPage } from './pageObjectModels/BreadCrumbPage'
export {
spyOnFetchEndpoint,
mockFetchEndpointResponse,
injectHeadersIntoFetch,
delayFetchForEndpoint,
-} from '@test/e2e/helpers/fetchOverride'
-export type { FetchOverrideHandle } from '@test/e2e/helpers/fetchOverride'
+} from './fetchOverride'
+export type { FetchOverrideHandle } from './fetchOverride'
export {
setupCleanTestPage,
setupTestPage,
selectTheme,
getThemePickerToggle,
-} from '@test/e2e/helpers/cookieHelper'
-export { wiremock, mocksEnabled } from '@test/e2e/helpers/mockServices'
+} from './cookieHelper'
+export { wiremock, mocksEnabled } from './mockServices'
diff --git a/test/e2e/helpers/pageObjectModels/BasePage.ts b/test/e2e/helpers/pageObjectModels/BasePage.ts
index d7108b29f..e0e20c084 100644
--- a/test/e2e/helpers/pageObjectModels/BasePage.ts
+++ b/test/e2e/helpers/pageObjectModels/BasePage.ts
@@ -435,18 +435,22 @@ export class BasePage {
* await page.waitForPageLoad()
* ```
*/
- async waitForPageLoad(): Promise {
+ async waitForPageLoad(options?: { requireNext?: boolean; timeout?: number }): Promise {
+ const requireNext = options?.requireNext ?? false
+ const timeout = options?.timeout ?? DEFAULT_NAVIGATION_TIMEOUT
const currentCount = await this._page.evaluate(() => window.__astroPageLoadCounter ?? 0)
- if (currentCount > this.lastAstroPageLoadCount) {
+ if (!requireNext && currentCount > this.lastAstroPageLoadCount) {
this.lastAstroPageLoadCount = currentCount
return
}
+ const baseline = requireNext ? currentCount : this.lastAstroPageLoadCount
+
await this._page.waitForFunction(
previousCount => (window.__astroPageLoadCounter ?? 0) > previousCount,
- currentCount,
- { timeout: DEFAULT_NAVIGATION_TIMEOUT }
+ baseline,
+ { timeout }
)
this.lastAstroPageLoadCount = await this._page.evaluate(() => window.__astroPageLoadCounter ?? 0)
diff --git a/test/e2e/helpers/pageObjectModels/BreadCrumbPage.ts b/test/e2e/helpers/pageObjectModels/BreadCrumbPage.ts
index 50ba2cd93..4eb3cfcf2 100644
--- a/test/e2e/helpers/pageObjectModels/BreadCrumbPage.ts
+++ b/test/e2e/helpers/pageObjectModels/BreadCrumbPage.ts
@@ -55,7 +55,7 @@ export class BreadCrumbPage extends BasePage {
return
}
- const waitForLoad = this.waitForPageLoad()
+ const waitForLoad = this.waitForPageLoad({ requireNext: true })
await this.click(`a[href="${targetHref}"]`)
await waitForLoad
}
diff --git a/test/e2e/specs/01-smoke/critical-paths.spec.ts b/test/e2e/specs/01-smoke/critical-paths.spec.ts
index a17f6eee6..937f161e0 100644
--- a/test/e2e/specs/01-smoke/critical-paths.spec.ts
+++ b/test/e2e/specs/01-smoke/critical-paths.spec.ts
@@ -24,7 +24,7 @@ test.describe('Critical Paths @smoke', () => {
const page = await BasePage.init(playwrightPage)
for (const { url: path } of page.navigationItems) {
await page.goto('/')
- const navigationComplete = page.waitForPageLoad()
+ const navigationComplete = page.waitForPageLoad({ requireNext: true })
await page.navigateToPage(path)
await navigationComplete
await playwrightPage.waitForFunction(() => {
@@ -56,7 +56,7 @@ test.describe('Critical Paths @smoke', () => {
await playwrightPage.waitForSelector('.menu-visible', { state: 'visible' })
// Click navigation link
- const navigationComplete = page.waitForPageLoad()
+ const navigationComplete = page.waitForPageLoad({ requireNext: true })
await page.click(`a[href="${path}"]`)
await navigationComplete
await playwrightPage.waitForFunction(() => {
From 65927989a566f166d61a0af15be8ac060cb6623c Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 21:15:00 +0300
Subject: [PATCH 02/49] Fix flaky E2E tests - added a devServerPort constant in
astro.config.ts (defaults to DEV_SERVER_PORT or 4321) and wired it into
vite.server.hmr.clientPort so Vite instructs the browser to negotiate HMR
websockets through the same externally exposed port Astro serves on
---
astro.config.ts | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/astro.config.ts b/astro.config.ts
index 98eb0551b..d9f06e412 100644
--- a/astro.config.ts
+++ b/astro.config.ts
@@ -35,6 +35,9 @@ import { privacyPolicyVersion } from './src/integrations/PrivacyPolicyVersion'
import { pwaDevAssetServer } from './src/lib/plugins/pwaDevAssetServer'
import { createSerializeFunction, pagesJsonWriter } from './src/integrations/sitemapSerialize'
+// Ensure Vite's HMR websocket connects through the same exposed dev server port used by Astro.
+const devServerPort = Number(process.env['DEV_SERVER_PORT'] ?? 4321)
+
const sharedTestIntegrations = [
icon(),
]
@@ -93,6 +96,11 @@ export default defineConfig({
site: getSiteUrl(),
trailingSlash: 'never',
vite: {
+ server: {
+ hmr: {
+ clientPort: devServerPort,
+ },
+ },
build: {
/** Source map generation must be turned on for Sentry. */
sourcemap: true,
From c1902f0c4e15f330d927d36258ad3b761f50b1c2 Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 21:35:20 +0300
Subject: [PATCH 03/49] Fix flaky E2E test cases - update BasePage.ts so
expectNoErrors() filters out known non-actionable Firefox/Vite HMR websocket
noise ('WebSocket closed without opened') before asserting
pageErrors().length. Added isIgnorablePageError() helper with documentation.
---
e2e-stress-run.sh | 41 +++++++++++++++++++
test/e2e/helpers/pageObjectModels/BasePage.ts | 17 +++++++-
2 files changed, 56 insertions(+), 2 deletions(-)
create mode 100755 e2e-stress-run.sh
diff --git a/e2e-stress-run.sh b/e2e-stress-run.sh
new file mode 100755
index 000000000..07180f00e
--- /dev/null
+++ b/e2e-stress-run.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+set -uo pipefail
+
+SUCCESS_FILE="/tmp/e2e-stress-run-success-count"
+
+if [[ ! -f .env.development ]]; then
+ echo "Missing .env.development file" >&2
+ exit 1
+fi
+
+# Load dev environment variables so every Playwright run matches local stress conditions.
+set -a
+source .env.development
+set +a
+
+if [[ ! -f "$SUCCESS_FILE" ]]; then
+ echo "0" > "$SUCCESS_FILE"
+fi
+
+while true; do
+ CI=1 FORCE_COLOR=1 E2E_MOCKS=1 npx playwright test
+ status=$?
+
+ if [[ $status -eq 0 ]]; then
+ count=$(<"$SUCCESS_FILE")
+ if ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ count=0
+ fi
+ count=$((count + 1))
+ echo "$count" > "$SUCCESS_FILE"
+ echo "Number of successful runs: $count"
+ else
+ count=$(<"$SUCCESS_FILE")
+ if ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ count=0
+ fi
+ echo "Number of successful runs before failure: $count"
+ exit $status
+ fi
+
+done
diff --git a/test/e2e/helpers/pageObjectModels/BasePage.ts b/test/e2e/helpers/pageObjectModels/BasePage.ts
index e0e20c084..aea37f2ab 100644
--- a/test/e2e/helpers/pageObjectModels/BasePage.ts
+++ b/test/e2e/helpers/pageObjectModels/BasePage.ts
@@ -862,8 +862,9 @@ export class BasePage {
async expectNoErrors(): Promise> {
await this.waitForPageComplete()
const errors = await this._page.pageErrors()
- expect(errors).toHaveLength(0)
- return await this._page.pageErrors()
+ const filteredErrors = errors.filter((error) => !this.isIgnorablePageError(error))
+ expect(filteredErrors).toHaveLength(0)
+ return filteredErrors
}
/**
@@ -1177,4 +1178,16 @@ export class BasePage {
await expect(label).toBeVisible()
await expect(label).toContainText(pattern)
}
+
+ /**
+ * Filter recurring non-actionable browser errors (e.g., Firefox HMR websockets) from pageErrors().
+ */
+ private isIgnorablePageError(error: Error): boolean {
+ const message = error?.message ?? ''
+
+ // Firefox occasionally surfaces this when Vite's HMR websocket retries during stress runs.
+ if (message.includes('WebSocket closed without opened')) return true
+
+ return false
+ }
}
\ No newline at end of file
From edb4054af5bc698237a778a3ef2af6a044c84cec Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 21:45:13 +0300
Subject: [PATCH 04/49] =?UTF-8?q?Fixes=20to=20Action=20workflow=20-=20add?=
=?UTF-8?q?=20id:=20vitest=20to=20the=20=E2=80=9CRun=20unit=20tests?=
=?UTF-8?q?=E2=80=9D=20step=20so=20downstream=20steps=20can=20see=20whethe?=
=?UTF-8?q?r=20coverage=20files=20were=20generated?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/build-and-test.yml | 3 ++-
e2e-stress-run.sh | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index d84a2144c..d2374d0e7 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -57,6 +57,7 @@ jobs:
run: npm run lint
- name: Run unit tests (Vitest — coverage with GitHub Actions reporter)
+ id: vitest
run: |
# Run vitest with coverage. The built-in 'github-actions' reporter
# (configured in vitest.config.ts) will create annotations.
@@ -65,7 +66,7 @@ jobs:
- name: Report Coverage
uses: davelosert/vitest-coverage-report-action@v2.9.0
- if: always()
+ if: ${{ always() && steps.vitest.outcome == 'success' && hashFiles('coverage/coverage-summary.json') != '' }}
with:
json-summary-path: './coverage/coverage-summary.json'
json-final-path: './coverage/coverage-final.json'
diff --git a/e2e-stress-run.sh b/e2e-stress-run.sh
index 07180f00e..f652f8763 100755
--- a/e2e-stress-run.sh
+++ b/e2e-stress-run.sh
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
+# End-to-end stress test runner script
set -uo pipefail
SUCCESS_FILE="/tmp/e2e-stress-run-success-count"
From 7c10fc60dfa6698fd7676077ed2fb178604bde36 Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 21:53:44 +0300
Subject: [PATCH 05/49] Fix Actions workflow - add a dedicated 'Sync Astro
types' step to build-and-test.yml so CI runs npm run sync before TypeScript
checks, typed rss.xml.ts by importing CollectionEntry and annotating the
article parameter in the RSS item map to satisfy noImplicitAny
---
.github/workflows/build-and-test.yml | 3 +++
src/pages/rss.xml.ts | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index d2374d0e7..fb19290f4 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -50,6 +50,9 @@ jobs:
- name: Install dependencies
run: npm ci --legacy-peer-deps
+ - name: Sync Astro types
+ run: npm run sync
+
- name: Run TypeScript check
run: npm run check
diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts
index a58636530..fcfd1739e 100644
--- a/src/pages/rss.xml.ts
+++ b/src/pages/rss.xml.ts
@@ -1,6 +1,7 @@
import rss from '@astrojs/rss'
import { getCollection } from 'astro:content'
import type { APIContext } from 'astro'
+import type { CollectionEntry } from 'astro:content'
export async function GET(context: APIContext) {
const articles = await getCollection('articles')
@@ -9,7 +10,7 @@ export async function GET(context: APIContext) {
description:
'Webstack Builders is a solo software development agency specializing in platform engineering.',
site: context.site ?? 'https://webstackbuilders.com',
- items: articles.map(article => ({
+ items: articles.map((article: CollectionEntry<'articles'>) => ({
title: article.data.title,
pubDate: article.data.publishDate,
description: article.data.description,
From e80ec57b501d364e4bff93265d3c6e2bfb0cdc0e Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 22:20:57 +0300
Subject: [PATCH 06/49] move __fixtures__ directout to parent of __tests__
---
package.json | 2 +-
.../Contact/client/__fixtures__/contactForm.fixture.astro | 5 +++++
.../client/__tests__/__fixtures__/contactForm.fixture.astro | 5 -----
src/components/Forms/Contact/client/__tests__/testUtils.ts | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
create mode 100644 src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro
delete mode 100644 src/components/Forms/Contact/client/__tests__/__fixtures__/contactForm.fixture.astro
diff --git a/package.json b/package.json
index b9760ffae..7198b7824 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
],
"scripts": {
"build": "cross-env NODE_ENV=production npm run lint && npx astro build",
- "check": "npm run lint && npx astro check",
+ "check": "npx astro check",
"clean": "FORCE_COLOR=1 npx rimraf dist && FORCE_COLOR=1 npx rimraf .astro",
"dev": "npm run sync && cross-env NODE_ENV=development FORCE_COLOR=1 npx astro dev",
"dev:env": "FORCE_COLOR=1 dotenv -e .env.development -- npm run dev",
diff --git a/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro b/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro
new file mode 100644
index 000000000..f99d3ccc5
--- /dev/null
+++ b/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro
@@ -0,0 +1,5 @@
+---
+import ContactForm from '../../index.astro'
+---
+
+
diff --git a/src/components/Forms/Contact/client/__tests__/__fixtures__/contactForm.fixture.astro b/src/components/Forms/Contact/client/__tests__/__fixtures__/contactForm.fixture.astro
deleted file mode 100644
index 148b79449..000000000
--- a/src/components/Forms/Contact/client/__tests__/__fixtures__/contactForm.fixture.astro
+++ /dev/null
@@ -1,5 +0,0 @@
----
-import ContactForm from '../../../index.astro'
----
-
-
diff --git a/src/components/Forms/Contact/client/__tests__/testUtils.ts b/src/components/Forms/Contact/client/__tests__/testUtils.ts
index 400b89ad8..b529c004b 100644
--- a/src/components/Forms/Contact/client/__tests__/testUtils.ts
+++ b/src/components/Forms/Contact/client/__tests__/testUtils.ts
@@ -1,7 +1,7 @@
import { expect } from 'vitest'
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { TestError } from '@test/errors'
-import ContactFormFixture from '@components/Forms/Contact/client/__tests__/__fixtures__/contactForm.fixture.astro'
+import ContactFormFixture from '@components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro'
import type { ContactFormElement } from '@components/Forms/Contact/client'
import { getContactFormElements } from '@components/Forms/Contact/client/selectors'
import type { ContactFormElements } from '@components/Forms/Contact/client/@types'
From 3e00732030d45f33b43067afa72a7f861de0682e Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 22:25:16 +0300
Subject: [PATCH 07/49] Fix astro check errors
---
.../Contact/client/__fixtures__/contactForm.fixture.astro | 4 ++--
src/components/Toasts/NetworkStatus/index.astro | 2 +-
src/pages/testing/carousel.astro | 4 ++--
src/pages/testing/consent-preferences.astro | 4 ++--
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro b/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro
index f99d3ccc5..207392fd0 100644
--- a/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro
+++ b/src/components/Forms/Contact/client/__fixtures__/contactForm.fixture.astro
@@ -1,5 +1,5 @@
---
-import ContactForm from '../../index.astro'
+import ContactFormComponent from '../../index.astro'
---
-
+
diff --git a/src/components/Toasts/NetworkStatus/index.astro b/src/components/Toasts/NetworkStatus/index.astro
index 138568bfa..f8c54e4aa 100644
--- a/src/components/Toasts/NetworkStatus/index.astro
+++ b/src/components/Toasts/NetworkStatus/index.astro
@@ -15,7 +15,7 @@ import styles from './index.module.css'
---
diff --git a/src/pages/testing/carousel.astro b/src/pages/testing/carousel.astro
index 8ede08d75..09dc5502c 100644
--- a/src/pages/testing/carousel.astro
+++ b/src/pages/testing/carousel.astro
@@ -1,6 +1,6 @@
---
import BaseLayout from '@layouts/BaseLayout.astro'
-import Carousel from '@components/Carousel/index.astro'
+import CarouselComponent from '@components/Carousel/index.astro'
const pageTitle = 'Carousel Testing Ground'
const pageDescription = 'Dedicated page for validating carousel interactions without relying on marketing pages.'
@@ -21,7 +21,7 @@ const pagePath = '/testing/carousel'
-
-
+
From 8295e355d11e5b9f65808fdf18f0b62bd1020fce Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Tue, 2 Dec 2025 22:42:28 +0300
Subject: [PATCH 08/49] Add heredoc .env file in Build and Test Action workflow
---
.github/workflows/build-and-test.yml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index fb19290f4..02d2e5228 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -88,6 +88,26 @@ jobs:
- name: Build Upstash mock image
run: docker buildx build --load -t wb/upstash-redis-local:test test/containers/upstash/local-proxy
+ - name: Generate container env file
+ shell: bash
+ run: |
+ set -euo pipefail
+ mkdir -p test/containers
+ : "${SUPABASE_SERVICE_ROLE_KEY:?SUPABASE_SERVICE_ROLE_KEY secret is required}"
+
+ cat < test/containers/.env
+ COMPOSE_PROJECT_NAME=wb-e2e
+ CONVERTKIT_HTTP_PORT=9010
+ RESEND_HTTP_PORT=9011
+ UPSTASH_HTTP_PORT=${UPSTASH_HTTP_PORT:-8079}
+ UPSTASH_REDIS_PORT=${UPSTASH_REDIS_PORT:-6380}
+ UPSTASH_TOKEN=${UPSTASH_TOKEN:-local-dev-token}
+ SUPABASE_PROJECT_NAME=wb-supabase
+ SUPABASE_HEALTH_TIMEOUT=${SUPABASE_HEALTH_TIMEOUT:-240}
+ SUPABASE_URL=${SUPABASE_URL:-http://127.0.0.1:54321}
+ SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
+ EOF
+
- name: Start mock containers
run: npm run containers:up
From e5075d3c41c74c5bf53a045dbc14a040627ba872 Mon Sep 17 00:00:00 2001
From: Kevin Brown
Date: Wed, 3 Dec 2025 02:06:17 +0300
Subject: [PATCH 09/49] Update CI workflow files to start dev server and remove
build step
---
.github/workflows/build-and-test.yml | 53 +-
.github/workflows/codeql.yml | 8 +-
.github/workflows/dependency-review.yml | 2 +-
.github/workflows/type-check.yml | 51 --
.vscode/settings.json | 1 +
E2E_STESS_TESTS.md | 66 +++
_TODO.md | 1 +
package-lock.json | 586 +++++++++----------
package.json | 22 +-
test/e2e/specs/07-performance/PERFORMANCE.md | 1 +
10 files changed, 389 insertions(+), 402 deletions(-)
delete mode 100644 .github/workflows/type-check.yml
create mode 100644 E2E_STESS_TESTS.md
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 02d2e5228..17957c5d1 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -1,11 +1,13 @@
# Runs build, unit tests, and E2E tests - gates deployment
-# @TODO: Push to a preview branch on Vercel, and run the E2E tests on that preview instead of from a dev server
-name: CI - Build & Test
+name: Build and Test
on:
push:
+ branches:
+ - main
pull_request:
- branches: [main]
+ branches:
+ - main
jobs:
build-and-test:
@@ -39,7 +41,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -53,7 +55,7 @@ jobs:
- name: Sync Astro types
run: npm run sync
- - name: Run TypeScript check
+ - name: Run Astro check
run: npm run check
- name: Run lint
@@ -65,7 +67,7 @@ jobs:
# Run vitest with coverage. The built-in 'github-actions' reporter
# (configured in vitest.config.ts) will create annotations.
# Coverage reporters (json-summary, json) are configured in vitest.config.ts.
- npx vitest run --coverage
+ npm run test:coverage
- name: Report Coverage
uses: davelosert/vitest-coverage-report-action@v2.9.0
@@ -74,11 +76,6 @@ jobs:
json-summary-path: './coverage/coverage-summary.json'
json-final-path: './coverage/coverage-final.json'
- - name: Build project
- run: npm run build
- env:
- NODE_ENV: production
-
- name: Install Playwright browsers
run: npx playwright install --with-deps
@@ -120,11 +117,41 @@ jobs:
- name: Apply Supabase migrations
run: npm run containers:supabase:db-push
+ - name: Start Astro dev server
+ run: |
+ npm run dev -- --host 0.0.0.0 > /tmp/astro-dev.log 2>&1 &
+ echo $! > /tmp/astro-dev.pid
+
+ - name: Wait for dev server
+ run: |
+ for attempt in $(seq 1 60); do
+ if curl -fsS http://127.0.0.1:4321 >/dev/null; then
+ echo "✅ Dev server is responding"
+ exit 0
+ fi
+ sleep 2
+ done
+ echo "❌ Dev server failed to start" >&2
+ if [ -f /tmp/astro-dev.log ]; then
+ echo '--- Astro dev server log ---'
+ cat /tmp/astro-dev.log
+ fi
+ exit 1
+
- name: Run Playwright E2E tests
run: npx playwright test
env:
CI: "1"
FORCE_COLOR: "1"
+ E2E_MOCKS: "1"
+
+ - name: Stop Astro dev server
+ if: always()
+ run: |
+ if [ -f /tmp/astro-dev.pid ]; then
+ kill $(cat /tmp/astro-dev.pid) || true
+ rm /tmp/astro-dev.pid
+ fi
- name: Upload Playwright report
uses: actions/upload-artifact@v4
@@ -159,7 +186,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v4
- name: Deploy to Vercel (Preview)
uses: amondnet/vercel-action@v41.1.4
@@ -205,7 +232,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v4
- name: Deploy to Vercel (Production)
uses: amondnet/vercel-action@v41.1.4
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index b6dd9d244..a789ea188 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -3,9 +3,11 @@ name: CodeQL Advanced Security Scanning
on:
push:
- branches: [main]
+ branches:
+ - main
pull_request:
- branches: [main]
+ branches:
+ - main
schedule:
# Run at 6 AM UTC every Monday
- cron: '0 6 * * 1'
@@ -26,7 +28,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 135b2252a..96510a0f6 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4
diff --git a/.github/workflows/type-check.yml b/.github/workflows/type-check.yml
deleted file mode 100644
index f13eebac8..000000000
--- a/.github/workflows/type-check.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-# Fast code quality checks (TypeScript + Linting)
-name: Code Quality Check
-
-on:
- push:
- pull_request:
- branches: [main]
-
-jobs:
- quality-check:
- name: Code Quality Check
- runs-on: ubuntu-latest
-
- # Define environment variables once at the job level
- # These will be available to ALL steps in this job
- # @TODO: Same issue with build-and-test.yml. These are real keys set on GitHub. But the production build only occurs on Vercel, and it's the only place that needs real keys. Once we set up a test framework using Docker containers for Suprabase, Upstash, etc., we should replace these keys with test keys.
- env:
- CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }}
- CONVERTKIT_FORM_ID: ${{ secrets.CONVERTKIT_FORM_ID }}
- CRON_SECRET: ${{ secrets.CRON_SECRET }}
- RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
- SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
- SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
- KV_URL: ${{ secrets.KV_URL }}
- KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }}
- KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }}
- KV_REST_API_READ_ONLY_TOKEN: ${{ secrets.KV_REST_API_READ_ONLY_TOKEN }}
- REDIS_URL: ${{ secrets.REDIS_URL }}
- WEBMENTION_IO_TOKEN: ${{ secrets.WEBMENTION_IO_TOKEN }}
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "22.x"
- cache: 'npm'
-
- - name: Install dependencies
- run: npm ci --legacy-peer-deps
-
- - name: Run TypeScript check
- run: npm run check
-
- - name: Run linting
- run: npm run lint
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 30f7b3dfa..ab4b772e1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -15,6 +15,7 @@
"Bridgy",
"bsky",
"Btns",
+ "Buildx",
"CASB",
"Centricity",
"cfduid",
diff --git a/E2E_STESS_TESTS.md b/E2E_STESS_TESTS.md
new file mode 100644
index 000000000..f0fe52136
--- /dev/null
+++ b/E2E_STESS_TESTS.md
@@ -0,0 +1,66 @@
+
+# E2E Stress Tests
+
+Our goal is to make the E2E test suite as deterministic as possible, so that we can use it as a gate for CI to make sure that commits and PRs on GitHub are not breaking existing code and can be merged to main. We've spent an entire day running the test cases and fixing errors. Each run, one or more new errors appear, we fix them, and do another run with the same result. The entire test suite seems very flaky.
+
+## Problems Areas
+
+### 1. Carousel / Testimonials Hydration Regressions
+
+- **Symptom:**
+
+`testimonials.spec.ts` consistently fails in WebKit because the target pagination dot never registers as selected (`Expected: 1 Received: 0`). Earlier stress runs also timed out waiting for `data-carousel-ready`, implying the custom element never finishes initialization.
+
+- **Diagnostics:**
+
+ - Inspect built HTML for `/testing/carousel` and the home page to confirm the inline `
+