Skip to content
Draft
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
59 changes: 59 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Playwright Smoke Tests

on:
pull_request:
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

jobs:
smoke-test:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
SANITY_INTERNAL_ENV: staging
SANITY_E2E_SESSION_TOKEN: ${{ secrets.SANITY_E2E_SESSION_TOKEN }}
SANITY_E2E_PROJECT_ID: ${{ vars.SANITY_E2E_PROJECT_ID }}
steps:
- uses: actions/checkout@v6

- uses: ./.github/actions/setup

- name: Build plugins and test studio
# Uses test-studio... (not test-studio^...) to include test-studio itself in the build,
# which is required for `sanity start` to serve the pre-built studio.
run: pnpm turbo run build --filter=test-studio...

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('dev/test-studio/package.json') }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm --filter test-studio exec playwright install --with-deps chromium

- name: Install Playwright system dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm --filter test-studio exec playwright install-deps chromium

- name: Run smoke tests
run: pnpm --filter test-studio exec playwright test --config e2e/playwright.config.ts

- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: dev/test-studio/e2e/results/
retention-days: 30
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,8 @@ vite.config.ts.timestamp-*

# Sanity Studio
.sanity

# Playwright
**/e2e/results/
**/playwright-report/
**/blob-report/
25 changes: 25 additions & 0 deletions dev/test-studio/e2e/globalSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {chromium, type FullConfig} from '@playwright/test'

const INIT_TIMEOUT_MS = 120_000

/**
* Global setup for smoke tests.
*
* Because the development server can be ready to receive requests but has not
* precompiled javascript, we wait here until the initial bundle is ready.
* This prevents each test from having to deal with the very different timeouts
* for the first and subsequent requests.
*/
export default async function globalSetup(config: FullConfig): Promise<void> {
const {baseURL = 'http://localhost:3333', contextOptions} = config.projects[0].use
const browser = await chromium.launch()
const context = await browser.newContext(contextOptions)
const page = await context.newPage()

await Promise.all([
page.waitForResponse('*/**/users/me*', {timeout: INIT_TIMEOUT_MS}),
page.goto(baseURL, {timeout: INIT_TIMEOUT_MS}),
])

await browser.close()
}
65 changes: 65 additions & 0 deletions dev/test-studio/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {defineConfig, devices} from '@playwright/test'

const CI = process.env.CI === 'true'
const BASE_URL = process.env.SANITY_E2E_BASE_URL || 'http://localhost:3333'
const PROJECT_ID = process.env.SANITY_E2E_PROJECT_ID || 'ppsg7ml5'
const TOKEN = process.env.SANITY_E2E_SESSION_TOKEN || ''

export default defineConfig({
globalSetup: './globalSetup',
testDir: '.',
testMatch: '**/*.spec.ts',
timeout: 60_000,
expect: {timeout: 30_000},
fullyParallel: true,
retries: CI ? 2 : 0,
reporter: CI ? [['list'], ['blob']] : [['list'], ['html', {open: 'never'}]],
outputDir: './results',

use: {
baseURL: BASE_URL,
actionTimeout: 10_000,
trace: 'on-first-retry',
video: 'retain-on-failure',
viewport: {width: 1728, height: 1000},
headless: true,
contextOptions: {reducedMotion: 'reduce'},
storageState: {
cookies: [],
origins: [
{
origin: BASE_URL,
localStorage: [
{
name: `__studio_auth_token_${PROJECT_ID}`,
value: JSON.stringify({
token: TOKEN,
time: new Date().toISOString(),
}),
},
],
},
],
},
},

projects: [
{
name: 'chromium',
use: {...devices['Desktop Chrome']},
},
],

webServer: {
command: CI ? 'pnpm sanity start' : 'pnpm sanity dev',
// The config lives in e2e/ but sanity CLI must run from the studio root (dev/test-studio/)
cwd: '..',
port: 3333,
reuseExistingServer: !CI,
stdout: 'pipe',
timeout: 120_000,
env: {
SANITY_INTERNAL_ENV: process.env.SANITY_INTERNAL_ENV || 'staging',
},
},
})
17 changes: 17 additions & 0 deletions dev/test-studio/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {expect, test} from '@playwright/test'

test.describe('Studio smoke test', () => {
test('studio loads the workspace selector', async ({page}) => {
await page.goto('/')
// Wait for the studio to fully render – workspace selector shows workspace links
await expect(page.getByRole('link', {name: /kitchen-sink/i})).toBeVisible()
})

test('kitchen-sink workspace loads', async ({page}) => {
await page.goto('/kitchen-sink')
// Wait for the studio navbar to appear, which indicates the workspace has loaded
await expect(page.getByRole('navigation')).toBeVisible()
// The studio should show the project's main UI (structure tool is the default)
await expect(page.getByRole('link', {name: /kitchen-sink/i})).toBeVisible()
})
})
1 change: 1 addition & 0 deletions dev/test-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"styled-components": "catalog:"
},
"devDependencies": {
"@playwright/test": "^1.59.1",
"@tsconfig/vite-react": "^7.0.2",
"@types/node": "catalog:",
"@types/react": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion dev/test-studio/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/vite-react/tsconfig.json",
"include": ["src/**/*", "migrations/**/*.ts", "sanity.cli.ts", "sanity.config.ts"],
"include": ["src/**/*", "migrations/**/*.ts", "sanity.cli.ts", "sanity.config.ts", "e2e/**/*"],
"compilerOptions": {
"customConditions": ["development"]
}
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading