diff --git a/.env.example b/.env.example
index 1774debc755..97176c6b28c 100644
--- a/.env.example
+++ b/.env.example
@@ -7,6 +7,8 @@
# You can generate your own token by heading over to the tokens-section of
# https://www.sanity.io/manage/, or by using your CLI user token (`sanity debug --secrets`)
SANITY_E2E_SESSION_TOKEN=
+SANITY_E2E_PROJECT_ID=
+SANITY_E2E_DATASET=
# Whether or not to run the end to end tests in headless mode. Defaults to true, but sometimes
# you might want to see the browser in action, in which case you can set this to `false`.
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 044a1e45796..0a791a2cf15 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -62,12 +62,27 @@ jobs:
- name: Build CLI
run: yarn build:cli # Needed for CLI tests
+ - name: Build E2E test studio
+ env:
+ # Update the SANITY_E2E_SESSION_TOKEN on github to the new value once this is merged to next
+ # Change the below to `secrets.SANITY_E2E_SESSION_TOKEN`
+ # Delete `SANITY_E2E_SESSION_TOKEN_NEW` from github
+ SANITY_E2E_SESSION_TOKEN: ${{ secrets.SANITY_E2E_SESSION_TOKEN_NEW }}
+ SANITY_E2E_PROJECT_ID: ${{ secrets.SANITY_E2E_PROJECT_ID }}
+ SANITY_E2E_DATASET: ${{ secrets.SANITY_E2E_DATASET }}
+ run: yarn e2e:build
+
- name: Run end-to-end tests
env:
- SANITY_E2E_SESSION_TOKEN: ${{ secrets.SANITY_E2E_SESSION_TOKEN }}
# Missing in docs but in use
# here https://github.com/microsoft/playwright/blob/main/packages/playwright/src/reporters/blob.ts#L108
PWTEST_BLOB_REPORT_NAME: ${{ matrix.project }}
+ # Update the SANITY_E2E_SESSION_TOKEN on github to the new value once this is merged to next
+ # Change the below to `secrets.SANITY_E2E_SESSION_TOKEN`
+ # Delete `SANITY_E2E_SESSION_TOKEN_NEW` from github
+ SANITY_E2E_SESSION_TOKEN: ${{ secrets.SANITY_E2E_SESSION_TOKEN_NEW }}
+ SANITY_E2E_PROJECT_ID: ${{ secrets.SANITY_E2E_PROJECT_ID }}
+ SANITY_E2E_DATASET: ${{ secrets.SANITY_E2E_DATASET }}
run: yarn test:e2e --project ${{ matrix.project }} --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v3
diff --git a/dev/studio-e2e-testing/.eslintrc b/dev/studio-e2e-testing/.eslintrc
new file mode 100644
index 00000000000..0fdc787ada5
--- /dev/null
+++ b/dev/studio-e2e-testing/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "@sanity/eslint-config-studio"
+}
diff --git a/dev/studio-e2e-testing/.gitignore b/dev/studio-e2e-testing/.gitignore
new file mode 100644
index 00000000000..aa9909c010f
--- /dev/null
+++ b/dev/studio-e2e-testing/.gitignore
@@ -0,0 +1,29 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# Dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# Compiled Sanity Studio
+/dist
+
+# Temporary Sanity runtime, generated by the CLI on every dev server start
+/.sanity
+
+# Logs
+/logs
+*.log
+
+# Coverage directory used by testing tools
+/coverage
+
+# Misc
+.DS_Store
+*.pem
+
+# Typescript
+*.tsbuildinfo
+
+# Dotenv and similar local-only files
+*.local
diff --git a/dev/studio-e2e-testing/README.md b/dev/studio-e2e-testing/README.md
new file mode 100644
index 00000000000..c4b842d72fa
--- /dev/null
+++ b/dev/studio-e2e-testing/README.md
@@ -0,0 +1,9 @@
+# Sanity Clean Content Studio
+
+Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend.
+
+Now you can do the following things:
+
+- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
+- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
+- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
diff --git a/dev/studio-e2e-testing/components/Branding.tsx b/dev/studio-e2e-testing/components/Branding.tsx
new file mode 100644
index 00000000000..39b34e83225
--- /dev/null
+++ b/dev/studio-e2e-testing/components/Branding.tsx
@@ -0,0 +1,10 @@
+import {Box, Text} from '@sanity/ui'
+import React from 'react'
+
+export function Branding() {
+ return (
+
+ E2E Test Studio™
+
+ )
+}
diff --git a/dev/studio-e2e-testing/package.json b/dev/studio-e2e-testing/package.json
new file mode 100644
index 00000000000..5452cd781b0
--- /dev/null
+++ b/dev/studio-e2e-testing/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "studio-e2e-testing",
+ "private": true,
+ "version": "3.18.1",
+ "license": "MIT",
+ "author": "Sanity.io ",
+ "scripts": {
+ "dev": "sanity dev --port 3339",
+ "start": "sanity start --port 3339",
+ "build": "sanity build"
+ },
+ "keywords": [
+ "sanity"
+ ],
+ "dependencies": {
+ "@sanity/google-maps-input": "^3.0.1",
+ "@sanity/icons": "^2.4.0",
+ "@sanity/ui": "^1.7.2",
+ "@sanity/vision": "3.18.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-is": "^18.2.0",
+ "sanity": "3.18.1",
+ "sanity-plugin-mux-input": "^2.2.1",
+ "sanity-test-studio": "3.18.1",
+ "styled-components": "^6.1.0"
+ }
+}
diff --git a/dev/studio-e2e-testing/sanity.cli.ts b/dev/studio-e2e-testing/sanity.cli.ts
new file mode 100644
index 00000000000..1143dff8b7f
--- /dev/null
+++ b/dev/studio-e2e-testing/sanity.cli.ts
@@ -0,0 +1,17 @@
+import {defineCliConfig} from 'sanity/cli'
+import {loadEnvFiles} from '../../scripts/utils/loadEnvFiles'
+
+loadEnvFiles()
+
+export default defineCliConfig({
+ api: {
+ projectId: process.env.SANITY_E2E_PROJECT_ID,
+ dataset: process.env.SANITY_E2E_DATASET,
+ },
+ vite: {
+ define: {
+ 'process.env.SANITY_E2E_PROJECT_ID': JSON.stringify(process.env.SANITY_E2E_PROJECT_ID),
+ 'process.env.SANITY_E2E_DATASET': JSON.stringify(process.env.SANITY_E2E_DATASET),
+ },
+ },
+})
diff --git a/dev/studio-e2e-testing/sanity.config.ts b/dev/studio-e2e-testing/sanity.config.ts
new file mode 100644
index 00000000000..c47d8012965
--- /dev/null
+++ b/dev/studio-e2e-testing/sanity.config.ts
@@ -0,0 +1,101 @@
+import {defineConfig, definePlugin} from 'sanity'
+import {deskTool} from 'sanity/desk'
+import {visionTool} from '@sanity/vision'
+import {BookIcon} from '@sanity/icons'
+import {muxInput} from 'sanity-plugin-mux-input'
+import {googleMapsInput} from '@sanity/google-maps-input'
+import {imageAssetSource} from 'sanity-test-studio/assetSources'
+import {resolveDocumentActions as documentActions} from 'sanity-test-studio/documentActions'
+import {resolveInitialValueTemplates} from 'sanity-test-studio/initialValueTemplates'
+import {languageFilter} from 'sanity-test-studio/plugins/language-filter'
+import {defaultDocumentNode, newDocumentOptions, structure} from 'sanity-test-studio/structure'
+import {presenceTool} from 'sanity-test-studio/plugins/presence'
+import {copyAction} from 'sanity-test-studio/fieldActions/copyAction'
+import {assistFieldActionGroup} from 'sanity-test-studio/fieldActions/assistFieldActionGroup'
+import {commentAction} from 'sanity-test-studio/fieldActions/commentFieldAction'
+import {customInspector} from 'sanity-test-studio/inspectors/custom'
+import {pasteAction} from 'sanity-test-studio/fieldActions/pasteAction'
+import {Branding} from './components/Branding'
+import {schemaTypes} from './schemas'
+
+const sharedSettings = definePlugin({
+ name: 'sharedSettings',
+ schema: {
+ types: schemaTypes,
+ templates: resolveInitialValueTemplates,
+ },
+ form: {
+ image: {
+ assetSources: [imageAssetSource],
+ },
+ },
+ studio: {
+ components: {
+ logo: Branding,
+ },
+ },
+ document: {
+ actions: documentActions,
+ inspectors: (prev, ctx) => {
+ if (ctx.documentType === 'inspectorsTest') {
+ return [customInspector, ...prev]
+ }
+
+ return prev
+ },
+ unstable_fieldActions: (prev, ctx) => {
+ if (['fieldActionsTest', 'stringsTest'].includes(ctx.documentType)) {
+ return [...prev, commentAction, assistFieldActionGroup, copyAction, pasteAction]
+ }
+
+ return prev
+ },
+ newDocumentOptions,
+ },
+ plugins: [
+ deskTool({
+ icon: BookIcon,
+ name: 'content',
+ title: 'Content',
+ structure,
+ defaultDocumentNode,
+ }),
+ languageFilter({
+ defaultLanguages: ['nb'],
+ supportedLanguages: [
+ {id: 'ar', title: 'Arabic'},
+ {id: 'en', title: 'English'},
+ {id: 'nb', title: 'Norwegian (bokmål)'},
+ {id: 'nn', title: 'Norwegian (nynorsk)'},
+ {id: 'pt', title: 'Portuguese'},
+ {id: 'es', title: 'Spanish'},
+ ],
+ types: ['languageFilterDebug'],
+ }),
+ googleMapsInput({
+ apiKey: 'AIzaSyDDO2FFi5wXaQdk88S1pQUa70bRtWuMhkI',
+ defaultZoom: 11,
+ defaultLocation: {
+ lat: 40.7058254,
+ lng: -74.1180863,
+ },
+ }),
+ visionTool({
+ defaultApiVersion: '2022-08-08',
+ }),
+ // eslint-disable-next-line camelcase
+ muxInput({mp4_support: 'standard'}),
+ presenceTool(),
+ ],
+})
+
+export default defineConfig({
+ name: 'default',
+ title: 'studio-e2e-testing',
+
+ projectId: process.env.SANITY_E2E_PROJECT_ID!,
+ dataset: process.env.SANITY_E2E_DATASET!,
+
+ plugins: [sharedSettings()],
+ basePath: '/test',
+})
diff --git a/dev/studio-e2e-testing/schemas/index.ts b/dev/studio-e2e-testing/schemas/index.ts
new file mode 100644
index 00000000000..c1745d8fcf3
--- /dev/null
+++ b/dev/studio-e2e-testing/schemas/index.ts
@@ -0,0 +1 @@
+export {schemaTypes} from 'sanity-test-studio/schema'
diff --git a/dev/studio-e2e-testing/static/.gitkeep b/dev/studio-e2e-testing/static/.gitkeep
new file mode 100644
index 00000000000..37178a72a54
--- /dev/null
+++ b/dev/studio-e2e-testing/static/.gitkeep
@@ -0,0 +1 @@
+Files placed here will be served by the Sanity server under the `/static`-prefix
diff --git a/dev/studio-e2e-testing/tsconfig.json b/dev/studio-e2e-testing/tsconfig.json
new file mode 100644
index 00000000000..dee6b84f246
--- /dev/null
+++ b/dev/studio-e2e-testing/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true
+ },
+ "include": ["**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
diff --git a/package.json b/package.json
index f5f5f5cd33c..21bae3b54c0 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,10 @@
"example:ecommerce-studio": "yarn --cwd examples/ecommerce-studio start",
"example:example-studio": "yarn --cwd dev/example-studio start",
"example:movies-studio": "yarn --cwd examples/movies-studio start",
+ "e2e:dev": "yarn --cwd dev/studio-e2e-testing dev",
+ "e2e:build": "yarn --cwd dev/studio-e2e-testing build",
+ "e2e:preview": "yarn e2e:build && yarn --cwd dev/studio-e2e-testing start",
+ "e2e:start": "yarn --cwd dev/studio-e2e-testing start",
"etl": "node -r dotenv-flow/config -r esbuild-register scripts/etl",
"init": "lerna clean --yes && run-s bootstrap build",
"lerna:clean": "lerna clean",
diff --git a/playwright.config.ts b/playwright.config.ts
index b2c60169360..fa4a1a04d9a 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -18,7 +18,9 @@ const OS_BROWSERS =
// Read environment variables
const CI = readBoolEnv('CI', false)
const E2E_DEBUG = readBoolEnv('SANITY_E2E_DEBUG', false)
-const PROJECT_ID = 'ppsg7ml5'
+const PROJECT_ID = process.env.SANITY_E2E_PROJECT_ID!
+
+const BASE_URL = 'http://localhost:3339/'
/**
* See https://playwright.dev/docs/test-configuration.
@@ -52,7 +54,7 @@ export default defineConfig({
actionTimeout: 10000,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
- baseURL: 'http://localhost:3333/',
+ baseURL: BASE_URL,
headless: readBoolEnv('SANITY_E2E_HEADLESS', !E2E_DEBUG),
storageState: getStorageStateForProjectId(PROJECT_ID),
viewport: {width: 1728, height: 1000},
@@ -87,9 +89,14 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
- command: 'yarn dev',
- port: 3333,
+ /**
+ * If it is running in CI just start the production build assuming that studio is already build
+ * Locally run the dev server
+ */
+ command: CI ? 'yarn e2e:start' : 'yarn e2e:dev',
+ port: 3339,
reuseExistingServer: !CI,
+ stdout: 'pipe',
},
})
@@ -123,7 +130,7 @@ function getStorageStateForProjectId(projectId: string) {
cookies: [],
origins: [
{
- origin: 'http://localhost:3333',
+ origin: BASE_URL,
localStorage: [
{
name: `__studio_auth_token_${projectId}`,
diff --git a/test/e2e/README.md b/test/e2e/README.md
index 5beb31a2337..bff97df76de 100644
--- a/test/e2e/README.md
+++ b/test/e2e/README.md
@@ -1,8 +1,12 @@
# E2E Testing in the Studio
-Before you get started with writing and running tests, you need to get hold of a token - either using your own Sanity user token (`sanity debug --secrets` will give you the CLI token provided you are logged in `sanity login`), or by creating a project API token using https://sanity.io/manage.
+## Required Env Variables
-The tests expects to find the token in an environment variable named `SANITY_E2E_SESSION_TOKEN`. Either define it in your shell, or add it to the `.env.local` file in the repository root.
+The tests expects to find the below env variables. Either define it in your shell, or add it to the `.env.local` file in the repository root.
+
+- `SANITY_E2E_SESSION_TOKEN`: Before you get started with writing and running tests, you need to get hold of a token - either using your own Sanity user token (`sanity debug --secrets` will give you the CLI token provided you are logged in `sanity login`), or by creating a project API token using https://sanity.io/manage.
+- `SANITY_E2E_PROJECT_ID`: Project ID of the studio
+- `SANITY_E2E_DATASET`: Dataset name of the studio
## Running tests
diff --git a/test/e2e/env.ts b/test/e2e/env.ts
index 674a5a38c6c..3b7f0f1860a 100644
--- a/test/e2e/env.ts
+++ b/test/e2e/env.ts
@@ -6,6 +6,8 @@ import {loadEnvFiles} from '../../scripts/utils/loadEnvFiles'
loadEnvFiles()
const SANITY_E2E_SESSION_TOKEN = process.env.SANITY_E2E_SESSION_TOKEN!
+const SANITY_E2E_PROJECT_ID = process.env.SANITY_E2E_PROJECT_ID!
+const SANITY_E2E_DATASET = process.env.SANITY_E2E_DATASET!
if (!SANITY_E2E_SESSION_TOKEN) {
console.error('Missing `SANITY_E2E_SESSION_TOKEN` environment variable.')
@@ -13,4 +15,16 @@ if (!SANITY_E2E_SESSION_TOKEN) {
process.exit(1)
}
-export {SANITY_E2E_SESSION_TOKEN}
+if (!SANITY_E2E_PROJECT_ID) {
+ console.error('Missing `SANITY_E2E_PROJECT_ID` environment variable.')
+ console.error('See `test/e2e/README.md` for details.')
+ process.exit(1)
+}
+
+if (!SANITY_E2E_DATASET) {
+ console.error('Missing `SANITY_E2E_DATASET` environment variable.')
+ console.error('See `test/e2e/README.md` for details.')
+ process.exit(1)
+}
+
+export {SANITY_E2E_SESSION_TOKEN, SANITY_E2E_PROJECT_ID, SANITY_E2E_DATASET}
diff --git a/test/e2e/globalSetup.ts b/test/e2e/globalSetup.ts
index 479a6410585..367b5055cb6 100644
--- a/test/e2e/globalSetup.ts
+++ b/test/e2e/globalSetup.ts
@@ -1,7 +1,7 @@
import {chromium, type FullConfig} from '@playwright/test'
const INIT_TIMEOUT_MS = 120000
-const FALLBACK_URL = 'http://localhost:3333/'
+const FALLBACK_URL = 'http://localhost:3339/'
/**
* Global setup for all end-to-end tests.
diff --git a/test/e2e/helpers/constants.ts b/test/e2e/helpers/constants.ts
index e97090918d2..da790ee9526 100644
--- a/test/e2e/helpers/constants.ts
+++ b/test/e2e/helpers/constants.ts
@@ -1,3 +1 @@
-export const STUDIO_DATASET_NAME = 'test'
-export const STUDIO_PROJECT_ID = 'ppsg7ml5'
export const STALE_TEST_THRESHOLD_MS = 10000
diff --git a/test/e2e/helpers/sanityClient.ts b/test/e2e/helpers/sanityClient.ts
index 79de448a044..b96871c2dc0 100644
--- a/test/e2e/helpers/sanityClient.ts
+++ b/test/e2e/helpers/sanityClient.ts
@@ -1,8 +1,7 @@
import {createClient} from '@sanity/client'
-import {SanityClient} from 'sanity'
-import {SANITY_E2E_SESSION_TOKEN} from '../env'
-import {STUDIO_DATASET_NAME, STUDIO_PROJECT_ID} from './constants'
import {uuid} from '@sanity/uuid'
+import {SANITY_E2E_SESSION_TOKEN, SANITY_E2E_DATASET, SANITY_E2E_PROJECT_ID} from '../env'
+import {SanityClient} from 'sanity'
export class TestContext {
client: SanityClient
@@ -13,13 +12,13 @@ export class TestContext {
documentIds = new Set()
- getUniqueDocumentId() {
+ getUniqueDocumentId(): string {
const documentId = uuid()
this.documentIds.add(documentId)
return documentId
}
- teardown() {
+ teardown(): void {
this.client.delete({
query: '*[_id in $ids]',
params: {ids: [...this.documentIds].map((id) => `drafts.${id}`)},
@@ -28,8 +27,8 @@ export class TestContext {
}
const testSanityClient = createClient({
- projectId: STUDIO_PROJECT_ID,
- dataset: STUDIO_DATASET_NAME,
+ projectId: SANITY_E2E_PROJECT_ID,
+ dataset: SANITY_E2E_DATASET,
token: SANITY_E2E_SESSION_TOKEN,
useCdn: false,
apiVersion: '2021-08-31',
diff --git a/test/e2e/tests/inputs/text.spec.ts b/test/e2e/tests/inputs/text.spec.ts
index 450fc49ebf2..ad0e77d2d3f 100644
--- a/test/e2e/tests/inputs/text.spec.ts
+++ b/test/e2e/tests/inputs/text.spec.ts
@@ -71,6 +71,8 @@ withDefaultClient((context) => {
currentExpectedValue = nextExpectedValue
expect(await field.inputValue()).toBe(currentExpectedValue)
expect(await getRemoteValue()).toBe(currentExpectedValue)
+
+ context.teardown()
})
})
})