Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 71 additions & 43 deletions .github/actions/setup-test-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,71 +11,99 @@ inputs:
autokitteh-image:
description: "AutoKitteh Docker image"
required: true
workers:
description: "Number of Playwright workers (each worker gets its own backend container)"
required: false
default: "4"

outputs:
backend-base-port:
description: "The base port for backend containers (worker 0 uses this port, worker 1 uses port+1, etc.)"
value: "9980"

runs:
using: "composite"
steps:
- name: Start AutoKitteh container
- name: Start AutoKitteh containers (one per worker)
shell: bash
env:
DESCOPE_PROJECT_ID: ${{ inputs.descope-project-id }}
WORKERS: ${{ inputs.workers }}
run: |
echo "Starting AutoKitteh container..."

# Start docker command in background and get its PID
CONTAINER_ID=$(timeout 900 docker run -d \
-p 9980:9980 \
-e AK_AUTHHTTPMIDDLEWARE__USE_DEFAULT_USER="false" \
-e AK_AUTHLOGINHTTPSVC__DESCOPE__ENABLED="true" \
-e AK_HTTP__CORS__ALLOWED_ORIGINS="http://localhost:8000" \
-e AK_AUTHSESSIONS__ALLOWED_CORS_COOKIE="true" \
-e AK_AUTHLOGINHTTPSVC__DESCOPE__PROJECT_ID="$DESCOPE_PROJECT_ID" \
-e AK_AUTHSESSIONS__COOKIE_KEYS="78d85f1c9fd9df7d3d459a75f1db315ef634dc854ba90bc5add3e6cb6f135bd6,d9591b1ab2d0e5de1fef96a5a8a50b883430884211f16a206f84ad57897f99d5" \
-e AK_AUTHJWTTOKENS__HMAC__SIGN_KEY="183b1c8f4c64b3450907b3859be0b94044d3c92a3116f02213585a85dd0cb154" \
-e AK_AUTHJWTTOKENS__ALGORITHM="hmac" \
${{ inputs.autokitteh-image }} up --mode=dev --config db.seed_commands="insert into orgs(created_by, created_at, org_id, display_name, name, updated_by, updated_at) values('7bcbb000-0000-0000-0000-000000000000', '2025-01-06 09:47:45.348066+00:00', '01943b03-6344-702a-9471-1b0098752177', 'Tests''s Personal Org', 'tests_personal_org', '00000000-0000-0000-0000-000000000000', '2025-01-06 11:47:45.348369+02:00');insert into users(user_id, email, display_name, created_by, created_at, default_org_id, updated_by, updated_at,status) values('01943b03-6345-7606-8e13-e319ae2f1929', 'test@autokitteh.com', 'Tests User', '7bcbb000-0000-0000-0000-000000000000', '2025-01-06 09:47:45.349399+00:00', '01943b03-6344-702a-9471-1b0098752177', '00000000-0000-0000-0000-000000000000', '2025-01-06 11:47:45.349424+02:00',1);insert into org_members(created_by, created_at, org_id, user_id, status, roles) values('00000000-0000-0000-0000-000000000000', '2025-01-06 11:47:45.350038+02:00', '01943b03-6344-702a-9471-1b0098752177', '01943b03-6345-7606-8e13-e319ae2f1929', 1, '[\"admin\"]');") || {
echo "Container startup failed or timed out after 900 seconds (15 minutes)"
exit 1
}
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
echo "AutoKitteh container started successfully with ID: $CONTAINER_ID"

- name: Setup logging
BASE_PORT=9980
CONTAINER_IDS=""

echo "🚀 Starting $WORKERS AutoKitteh containers (one per Playwright worker)..."

for ((w=0; w<WORKERS; w++)); do
PORT=$((BASE_PORT + w))
echo "Starting container for worker $w on port $PORT..."

CONTAINER_ID=$(timeout 900 docker run -d \
--name "ak-worker-$w" \
-p ${PORT}:9980 \
-e AK_AUTHHTTPMIDDLEWARE__USE_DEFAULT_USER="false" \
-e AK_AUTHLOGINHTTPSVC__DESCOPE__ENABLED="true" \
-e AK_HTTP__CORS__ALLOWED_ORIGINS="http://localhost:8000" \
-e AK_AUTHSESSIONS__ALLOWED_CORS_COOKIE="true" \
-e AK_AUTHLOGINHTTPSVC__DESCOPE__PROJECT_ID="$DESCOPE_PROJECT_ID" \
-e AK_AUTHSESSIONS__COOKIE_KEYS="78d85f1c9fd9df7d3d459a75f1db315ef634dc854ba90bc5add3e6cb6f135bd6,d9591b1ab2d0e5de1fef96a5a8a50b883430884211f16a206f84ad57897f99d5" \
-e AK_AUTHJWTTOKENS__HMAC__SIGN_KEY="183b1c8f4c64b3450907b3859be0b94044d3c92a3116f02213585a85dd0cb154" \
-e AK_AUTHJWTTOKENS__ALGORITHM="hmac" \
${{ inputs.autokitteh-image }} up --mode=dev --config db.seed_commands="insert into orgs(created_by, created_at, org_id, display_name, name, updated_by, updated_at) values('7bcbb000-0000-0000-0000-000000000000', '2025-01-06 09:47:45.348066+00:00', '01943b03-6344-702a-9471-1b0098752177', 'Tests''s Personal Org', 'tests_personal_org', '00000000-0000-0000-0000-000000000000', '2025-01-06 11:47:45.348369+02:00');insert into users(user_id, email, display_name, created_by, created_at, default_org_id, updated_by, updated_at,status) values('01943b03-6345-7606-8e13-e319ae2f1929', 'test@autokitteh.com', 'Tests User', '7bcbb000-0000-0000-0000-000000000000', '2025-01-06 09:47:45.349399+00:00', '01943b03-6344-702a-9471-1b0098752177', '00000000-0000-0000-0000-000000000000', '2025-01-06 11:47:45.349424+02:00',1);insert into org_members(created_by, created_at, org_id, user_id, status, roles) values('00000000-0000-0000-0000-000000000000', '2025-01-06 11:47:45.350038+02:00', '01943b03-6344-702a-9471-1b0098752177', '01943b03-6345-7606-8e13-e319ae2f1929', 1, '[\"admin\"]');") || {
echo "Container startup failed for worker $w"
exit 1
}

CONTAINER_IDS="${CONTAINER_IDS}${CONTAINER_ID},"
echo "✓ Container for worker $w started: $CONTAINER_ID"
done

echo "CONTAINER_IDS=${CONTAINER_IDS%,}" >> $GITHUB_ENV
echo "WORKERS=$WORKERS" >> $GITHUB_ENV
echo "BASE_PORT=$BASE_PORT" >> $GITHUB_ENV
echo "✅ All $WORKERS containers started successfully"

- name: Setup logging for all containers
shell: bash
run: |
docker logs -f ${{ env.CONTAINER_ID }} > docker-logs.txt &
echo "LOGS_PID=$!" >> $GITHUB_ENV
for ((w=0; w<${{ env.WORKERS }}; w++)); do
docker logs -f "ak-worker-$w" > "docker-logs-worker-$w.txt" 2>&1 &
done
echo "📋 Logging enabled for all containers"

- name: Wait for Backend
- name: Wait for all backends
shell: bash
run: |
source "${{ github.action_path }}/scripts/progress-bar.sh"

URL=http://localhost:9980
echo "Waiting for AutoKitteh backend to start..."
TIMEOUT=300 # 5 minutes timeout
TIMEOUT=300
ITERATIONS=$((TIMEOUT / 2))

for ((i=1; i<=ITERATIONS; i++)); do
if curl --output /dev/null --silent --head --fail -X POST $URL; then
printf "\r✅ AutoKitteh backend ready in $((i * 2)) seconds! \n"
break
fi
echo "⏳ Waiting for ${{ env.WORKERS }} backends to be ready..."

if [[ -t 1 ]]; then
ProgressBar ${i} ${ITERATIONS} "AutoKitteh"
else
echo "Waiting for AutoKitteh... ($((i * 2))s elapsed)"
fi
for ((w=0; w<${{ env.WORKERS }}; w++)); do
PORT=$((${{ env.BASE_PORT }} + w))
URL="http://localhost:${PORT}"
echo "Waiting for backend on port $PORT (worker $w)..."

if [ $i -eq $ITERATIONS ]; then
printf "\n❌ Timeout waiting for backend after $TIMEOUT seconds\n"
exit 1
fi
for ((i=1; i<=ITERATIONS; i++)); do
if curl --output /dev/null --silent --head --fail -X POST $URL; then
echo "✅ Backend for worker $w ready on port $PORT"
break
fi

if [ $i -eq $ITERATIONS ]; then
echo "❌ Timeout waiting for backend on port $PORT"
exit 1
fi

sleep 2
sleep 2
done
done

echo "🎉 All ${{ env.WORKERS }} backends are ready!"

# - name: Install browser-specific dependencies
# if: inputs.browser == 'Safari'
# shell: bash
Expand Down
63 changes: 53 additions & 10 deletions .github/workflows/build_test_and_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,55 @@ jobs:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('package-lock.json') }}

test:
name: 🧪 Testing ${{ matrix.browser }}
test-build:
name: 🏗️ Build for Tests
needs: setup
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
submodules: true

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"

- name: Restore cached node_modules
uses: actions/cache/restore@v4
with:
path: node_modules
key: node-modules-${{ hashFiles('package-lock.json') }}

- name: Install dependencies (fallback)
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci

- name: Build for tests
env:
VITE_DESCOPE_PROJECT_ID: ${{ secrets.VITE_DESCOPE_PROJECT_ID }}
VITE_DISPLAY_CHATBOT: true
run: npm run build

- name: Upload test build
uses: actions/upload-artifact@v4
with:
name: test-built-files
path: dist
retention-days: 1

test:
name: 🧪 ${{ matrix.browser }} (shard ${{ matrix.shard }}/4)
needs: [setup, test-build]
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
timeout-minutes: 30
strategy:
matrix:
browser: ["Chrome", "Edge", "Firefox", "Safari"]
fail-fast: true
shard: [1, 2, 3, 4]
fail-fast: false

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -109,6 +148,12 @@ jobs:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('package-lock.json') }}

- name: Download test build
uses: actions/download-artifact@v4
with:
name: test-built-files
path: ./dist

- name: Cache Docker images
id: cache-docker
uses: ./.github/actions/cache-docker-images
Expand All @@ -122,7 +167,7 @@ jobs:
if: always()
run: |
{
echo "## 🗂️ Docker Cache Metrics (${{ matrix.browser }})"
echo "## 🗂️ Docker Cache Metrics (${{ matrix.browser }} shard ${{ matrix.shard }})"
echo ""
echo "| Metric | Value |"
echo "|--------|-------|"
Expand All @@ -140,23 +185,21 @@ jobs:
descope-project-id: ${{ secrets.VITE_DESCOPE_PROJECT_ID }}
browser: ${{ matrix.browser }}
autokitteh-image: ${{ vars.AUTOKITTEH_IMAGE }}
workers: "4"

- name: Generate connection test data
run: npm run generate:connection-test-data

- name: Run Playwright tests
env:
TESTS_JWT_AUTH_TOKEN: ${{ secrets.TESTS_JWT_AUTH_TOKEN }}
VITE_DESCOPE_PROJECT_ID: ${{ secrets.VITE_DESCOPE_PROJECT_ID }}
VITE_DISPLAY_CHATBOT: true
CI: true
run: npx playwright test --project=${{ matrix.browser }}
run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shard }}/4

- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
browser: ${{ matrix.browser }}
browser: ${{ matrix.browser }}-shard-${{ matrix.shard }}
run-id: ${{ github.run_id }}

visual-regression:
Expand Down
42 changes: 36 additions & 6 deletions e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,45 @@ import { OrgConnectionsPage, ConnectionsConfig, DashboardPage, ProjectPage } fro
// eslint-disable-next-line @typescript-eslint/naming-convention
const RATE_LIMIT_DELAY = process.env.E2E_RATE_LIMIT_DELAY ? parseInt(process.env.E2E_RATE_LIMIT_DELAY, 10) : 0;

// eslint-disable-next-line @typescript-eslint/naming-convention
const BACKEND_BASE_PORT = 9980;

type PageFixtureArgs = { page: Page };
type UseFixture<T> = (fixture: T) => Promise<void>;

const test = base.extend<{
connectionsConfig: ConnectionsConfig;
dashboardPage: DashboardPage;
orgConnectionsPage: OrgConnectionsPage;
projectPage: ProjectPage;
}>({
const test = base.extend<
{
connectionsConfig: ConnectionsConfig;
dashboardPage: DashboardPage;
orgConnectionsPage: OrgConnectionsPage;
projectPage: ProjectPage;
},
{ workerBackendPort: number }
>({
workerBackendPort: [
// eslint-disable-next-line no-empty-pattern
async ({}, use, workerInfo) => {
const port = BACKEND_BASE_PORT + workerInfo.workerIndex;
await use(port);
},
{ scope: "worker" },
],

page: async ({ page, workerBackendPort }, use) => {
if (workerBackendPort !== BACKEND_BASE_PORT) {
await page.route(`http://localhost:${BACKEND_BASE_PORT}/**`, async (route) => {
const originalUrl = route.request().url();
const newUrl = originalUrl.replace(
`http://localhost:${BACKEND_BASE_PORT}`,
`http://localhost:${workerBackendPort}`
);
const response = await route.fetch({ url: newUrl });
await route.fulfill({ response });
});
}
await use(page);
},

connectionsConfig: async ({ page }: PageFixtureArgs, use: UseFixture<ConnectionsConfig>) => {
await use(new ConnectionsConfig(page));
},
Expand Down
10 changes: 5 additions & 5 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default defineConfig({

maxFailures: process.env.CI ? 1 : undefined,

workers: process.env.CI ? 1 : 2,
workers: process.env.CI ? 4 : 2,

/* Configure projects for major browsers */
projects: [
Expand Down Expand Up @@ -88,7 +88,7 @@ export default defineConfig({

timeout: 60 * 1000 * 6, // 6 minutes timeout for each test

retries: process.env.CI ? 2 : 0, // 2 retries for CI, 0 for local
retries: process.env.CI ? 1 : 0, // 1 retry for CI, 0 for local

/* Visual regression test settings */
expect: {
Expand Down Expand Up @@ -119,15 +119,15 @@ export default defineConfig({

viewport: { width: 1920, height: 1080 },

actionTimeout: 3000,
actionTimeout: 6000,
},

webServer: {
command: `npm run build && npm run preview`,
command: process.env.CI ? `npm run preview` : `npm run build && npm run preview`,
port: previewPort,
reuseExistingServer: !process.env.CI,
stderr: "pipe",
stdout: "pipe",
timeout: 5 * 60 * 1000, // 300,000 ms = 5 minutes (CI builds can be slow)
timeout: process.env.CI ? 60 * 1000 : 5 * 60 * 1000,
},
});
Loading