Skip to content
Merged
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
123 changes: 112 additions & 11 deletions .github/workflows/studio-e2e-test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Studio E2E Tests
name: Selfhosted Studio E2E Tests
on:
push:
branches: [master]
Expand All @@ -22,28 +22,33 @@ concurrency:

permissions:
contents: write
pull-requests: write

jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
# Make the job non-blocking
continue-on-error: true
# Require approval only for external contributors
environment: ${{ github.event.pull_request.author_association != 'MEMBER' && 'Studio E2E Tests' || '' }}

env:
EMAIL: ${{ secrets.CI_EMAIL }}
PASSWORD: ${{ secrets.CI_PASSWORD }}
PROJECT_REF: ${{ secrets.CI_PROJECT_REF }}
NEXT_PUBLIC_IS_PLATFORM: true
NEXT_PUBLIC_API_URL: https://api.supabase.green
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_STUDIO_HOSTED_PROJECT_ID }}
# Studio Self-Hosted project ID
VERCEL_PROJECT_ID: prj_CnatEuo7L6bUZAgmujMrm5P1rxtv
NEXT_PUBLIC_HCAPTCHA_SITE_KEY: 10000000-ffff-ffff-ffff-000000000001
VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO }}

steps:
- uses: actions/checkout@v4
- name: Verify Vercel bypass secret exists
run: |
if [ -z "${{ secrets.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO }}" ]; then
echo "Required secret VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO is not set" >&2
exit 1
fi
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
Expand All @@ -57,13 +62,16 @@ jobs:
- name: Install dependencies
run: pnpm i

# Deploy a preview to Vercel (CLI mode) and capture the URL
- name: Install Vercel CLI
run: pnpm add --global vercel@latest

- name: Pull Vercel Environment Information (Preview)
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}

- name: Build Project Artifacts for Vercel
- name: Build Project Artifacts for Vercel (is_platform=false)
env:
NEXT_PUBLIC_IS_PLATFORM: false
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}

- name: Deploy Project to Vercel and Get URL
Expand All @@ -74,13 +82,16 @@ jobs:
echo "DEPLOY_URL=$DEPLOY_URL" >> $GITHUB_OUTPUT

- name: Install Playwright Browsers
run: pnpm -C e2e/studio exec playwright install --with-deps
run: pnpm -C e2e/studio exec playwright install chromium --with-deps --only-shell

- name: Run Playwright tests
- name: 🚀 Run Playwright tests against Vercel Preview
id: playwright
continue-on-error: true
env:
AUTHENTICATION: true
STUDIO_URL: ${{ steps.deploy_vercel.outputs.DEPLOY_URL }}/dashboard
AUTHENTICATION: false
STUDIO_URL: ${{ steps.deploy_vercel.outputs.DEPLOY_URL }}
API_URL: ${{ steps.deploy_vercel.outputs.DEPLOY_URL }}
VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO }}
run: pnpm e2e

- uses: actions/upload-artifact@v4
Expand All @@ -91,3 +102,93 @@ jobs:
e2e/studio/playwright-report/
e2e/studio/test-results/
retention-days: 7

- name: Prepare summary (outputs)
if: always()
id: summarize
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const p = 'e2e/studio/test-results/test-results.json'
// Initialize a summary object to hold test statistics.
let s={total:0,passed:0,failed:0,skipped:0,timedOut:0,interrupted:0,flaky:0,durationMs:0,note:''}
try {
const data = JSON.parse(fs.readFileSync(p,'utf8'))
// Recursively walk through the test suites to process each test.
const walk=suite=>{
if(!suite)return;
suite.specs?.forEach(spec=>{
spec.tests?.forEach(test=>{
s.total++;
// Get the last result of the test, as tests can be retried.
const lastResult = test.results[test.results.length - 1];
s.durationMs += lastResult.duration || 0;
// A test is considered flaky if it has more than one run and the final status is 'passed'.
if (test.results.length > 1 && lastResult.status === 'passed') {
s.flaky++
}
const status = lastResult.status === 'passed' && s.flaky > 0 ? 'flaky' : lastResult.status
s[status] = (s[status]||0)+1;
})
})
suite.suites?.forEach(walk)
}
walk(data.suites?.[0])
} catch { s.note='No JSON report found or parse error.' }
// Generate the markdown for the summary comment.
const md = s.note ? `Note: ${s.note}` : `- Total: ${s.total}\n- Passed: ${s.passed||0}\n- Failed: ${s.failed||0}\n- Skipped: ${s.skipped||0}\n- Timed out: ${s.timedOut||0}\n- Interrupted: ${s.interrupted||0}\n- Flaky: ${s.flaky||0}\n- Duration: ${(s.durationMs/1000).toFixed(1)}s`
// Set the summary and flaky_count as outputs for subsequent steps.
core.setOutput('summary', md)
core.setOutput('flaky_count', s.flaky)

- name: Comment summary on PR
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner
const repo = context.repo.repo
const issue_number = context.issue.number
const summary = `${{ steps.summarize.outputs.summary }}`.replace(/^"|"$/g,'')
const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}`
const marker = '<!-- studio-e2e-summary -->'

const now = new Date()
const weekday = now.toLocaleString('en-US', { weekday: 'long', timeZone: 'UTC' })
const day = now.toLocaleString('en-US', { day: 'numeric', timeZone: 'UTC' })
const month = now.toLocaleString('en-US', { month: 'long', timeZone: 'UTC' })
const year = now.toLocaleString('en-US', { year: 'numeric', timeZone: 'UTC' })
const time = now.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
})
const date = `${weekday} ${day}, ${month}, ${year} ${time} (UTC)`

const body = [
marker,
`**Studio E2E Results**`,
'',
summary,
'',
`Artifacts: ${runUrl}`,
'',
`Last updated: ${date}`
].join('\n')

const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number, per_page: 100 })
const existing = comments.find(c => c.body && c.body.includes(marker))
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body })
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body })
}

- name: Fail job if tests failed
if: steps.playwright.outcome != 'success' || steps.summarize.outputs.flaky_count > 0
run: |
echo "E2E tests failed" >&2
exit 1
1 change: 1 addition & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request:
branches:
- 'master'
merge_group:

# Cancel old builds on new commit for same workflow + branch/PR
concurrency:
Expand Down
2 changes: 1 addition & 1 deletion apps/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"postcss": "^8.5.3",
"rimraf": "^4.1.3",
"shiki": "^1.1.7",
"tailwindcss": "^3.3.0",
"tailwindcss": "catalog:",
"tsconfig": "workspace:*",
"tsx": "^4.19.3",
"typescript": "~5.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function ReportChartUpsell({
onMouseLeave={() => setIsHoveringUpgrade(false)}
className="mt-4"
>
<Link href={`/org/${orgSlug}/billing?panel=subscriptionPlan&source=reports`}>
<Link href={`/org/${orgSlug || '_'}/billing?panel=subscriptionPlan&source=reports`}>
Upgrade to{' '}
<span className="capitalize">
{!!report.availableIn?.length ? report.availableIn[0] : 'Pro'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { get, handleError } from 'data/fetchers'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import type { ResponseError } from 'types'
import { organizationKeys } from './keys'
import { IS_PLATFORM } from 'common'

export type OrganizationCustomerProfileVariables = {
slug?: string
Expand Down Expand Up @@ -52,7 +53,7 @@ export const useOrganizationCustomerProfileQuery = <TData = OrganizationCustomer
organizationKeys.customerProfile(slug),
({ signal }) => getOrganizationCustomerProfile({ slug }, signal),
{
enabled: enabled && canReadCustomerProfile && typeof slug !== 'undefined',
enabled: IS_PLATFORM && enabled && canReadCustomerProfile && typeof slug !== 'undefined',
...options,
}
)
Expand Down
6 changes: 2 additions & 4 deletions apps/studio/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,10 +531,8 @@ const nextConfig = {
pagesBufferLength: 100,
},
typescript: {
// On previews, typechecking is run via GitHub Action only for efficiency
// On production, we turn it on to prevent errors from conflicting PRs getting into
// prod
ignoreBuildErrors: process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' ? false : true,
// Typechecking is run via GitHub Action only for efficiency.
ignoreBuildErrors: true,
},
eslint: {
// We are already running linting via GH action, this will skip linting during production build on Vercel
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
"prettier": "3.2.4",
"raw-loader": "^4.0.2",
"require-in-the-middle": "^7.5.2",
"tailwindcss": "^3.4.1",
"tailwindcss": "catalog:",
"tsx": "^4.19.3",
"typescript": "~5.5.0",
"vite": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/state/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ function createTabsState(projectRef: string) {
router.push(`/project/${router.query.ref}/editor`)
break
default:
router.push(`/project/${router.query.ref}/${editor}`)
router.push(`/project/${router.query.ref}/${editor === 'table' ? 'editor' : 'sql'}`)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/ui-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"rimraf": "^4.1.3",
"shadcn": "^2.10.0",
"shiki": "^1.1.7",
"tailwindcss": "^3.3.0",
"tailwindcss": "catalog:",
"tsconfig": "workspace:*",
"tsx": "^4.19.3",
"typescript": "~5.5.0",
Expand Down
10 changes: 9 additions & 1 deletion e2e/studio/env.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import path from 'path'

const toBoolean = (value?: string) => {
if (value == null) return false
const normalized = value.trim().toLowerCase()
return normalized === 'true'
}

export const env = {
STUDIO_URL: process.env.STUDIO_URL,
API_URL: process.env.API_URL || 'https://api.supabase.green',
AUTHENTICATION: process.env.AUTHENTICATION,
AUTHENTICATION: toBoolean(process.env.AUTHENTICATION),
EMAIL: process.env.EMAIL,
PASSWORD: process.env.PASSWORD,
PROJECT_REF: process.env.PROJECT_REF || 'default',
IS_PLATFORM: process.env.IS_PLATFORM || 'false',
VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO:
process.env.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO || 'false',
}

export const STORAGE_STATE_PATH = path.join(__dirname, './playwright/.auth/user.json')
12 changes: 12 additions & 0 deletions e2e/studio/features/_global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ setup('Global Setup', async ({ page }) => {
- Is Platform: ${IS_PLATFORM}
`)

/*
* Check if we're in CI, if so, check VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO
* is set to true.
*/
const VERCEL_BYPASS = process.env.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO

if (process.env.CI === 'true') {
if (!VERCEL_BYPASS || VERCEL_BYPASS.length === 0) {
throw new Error('VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO is not set')
}
}

/**
* Studio Check
*/
Expand Down
Loading
Loading