diff --git a/CODEOWNERS b/CODEOWNERS index 5ed2da00b6f4..4ed754fbc2e5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -62,4 +62,4 @@ # /code/renderers/web-components/ @kasperpeulen @JReinhold # E2E -# /code/e2e-tests/ @yannbf @valentinpalkovic +# /code/e2e-sandbox/ @yannbf @valentinpalkovic diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd2886782808..593cb8d23b31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,7 +90,8 @@ Here's a highlight of notable directories and files: │ ├── builders │ ├── chromatic.config.json │ ├── core # Core package for UI and API of Storybook -│ ├── e2e-tests +│ ├── e2e-internal # Playwright e2e tests for the internal Storybook UI +│ ├── e2e-sandbox # Playwright e2e tests for generated sandboxes │ ├── frameworks # Different framework-bundler versions of Storybook │ ├── lib # CLI and plugins │ ├── node_modules diff --git a/code/.eslintrc.js b/code/.eslintrc.js index c856ed13b790..1626f9ca7b81 100644 --- a/code/.eslintrc.js +++ b/code/.eslintrc.js @@ -201,7 +201,7 @@ module.exports = { }, }, { - files: ['./e2e-tests/*.ts'], + files: ['./e2e-sandbox/*.ts', './e2e-internal/*.ts'], extends: ['plugin:playwright/recommended'], rules: { 'playwright/no-skipped-test': [ diff --git a/code/e2e-internal/internal-storybook.smoke.spec.ts b/code/e2e-internal/internal-storybook.smoke.spec.ts new file mode 100644 index 000000000000..3c0fccb5c0d1 --- /dev/null +++ b/code/e2e-internal/internal-storybook.smoke.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from '@playwright/test'; +import process from 'process'; + +/** + * Smoke tests for the internal Storybook UI (`code/.storybook`), not sandbox templates. + * + * Run locally (from repo root) with internal Storybook on port 6006: + * cd code && yarn storybook:ui + * yarn task e2e-tests-internal --no-link -s e2e-tests-internal + * # or: yarn playwright test -c e2e-internal/playwright.config.ts + */ + +const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:6006'; + +/** Stable core template story shipped with the internal UI. */ +const STORY_PATH = '/story/core-basics--basic'; + +test.describe('internal Storybook UI', () => { + test('loads a story in the preview iframe', async ({ page }) => { + await page.goto(`${storybookUrl}/?path=${STORY_PATH}`); + await expect(page.locator('#storybook-preview-iframe')).toBeVisible(); + + const preview = page.frameLocator('#storybook-preview-iframe'); + await expect(preview.getByRole('button', { name: 'Click Me!' })).toBeVisible(); + }); +}); diff --git a/code/e2e-internal/playwright.config.ts b/code/e2e-internal/playwright.config.ts new file mode 100644 index 000000000000..a241cd004f58 --- /dev/null +++ b/code/e2e-internal/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Playwright config for internal Storybook UI e2e (`code/.storybook`). + * Sandbox e2e uses `code/playwright.config.ts` and `code/e2e-sandbox/`. + */ +export default defineConfig({ + testDir: '.', + timeout: 30 * 1000, + expect: { + timeout: 5000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.PLAYWRIGHT_JUNIT_OUTPUT_NAME + ? [ + [ + 'junit', + { + embedAnnotationsAsProperties: true, + outputFile: process.env.PLAYWRIGHT_JUNIT_OUTPUT_NAME, + }, + ], + ] + : 'html', + use: { + ...devices['Desktop Chrome'], + actionTimeout: 0, + trace: 'retain-on-failure', + }, + outputDir: '../playwright-results/', +}); diff --git a/code/e2e-tests/addon-a11y.spec.ts b/code/e2e-sandbox/addon-a11y.spec.ts similarity index 100% rename from code/e2e-tests/addon-a11y.spec.ts rename to code/e2e-sandbox/addon-a11y.spec.ts diff --git a/code/e2e-tests/addon-actions.spec.ts b/code/e2e-sandbox/addon-actions.spec.ts similarity index 100% rename from code/e2e-tests/addon-actions.spec.ts rename to code/e2e-sandbox/addon-actions.spec.ts diff --git a/code/e2e-tests/addon-backgrounds.spec.ts b/code/e2e-sandbox/addon-backgrounds.spec.ts similarity index 100% rename from code/e2e-tests/addon-backgrounds.spec.ts rename to code/e2e-sandbox/addon-backgrounds.spec.ts diff --git a/code/e2e-tests/addon-controls.spec.ts b/code/e2e-sandbox/addon-controls.spec.ts similarity index 100% rename from code/e2e-tests/addon-controls.spec.ts rename to code/e2e-sandbox/addon-controls.spec.ts diff --git a/code/e2e-tests/addon-docs.spec.ts b/code/e2e-sandbox/addon-docs.spec.ts similarity index 100% rename from code/e2e-tests/addon-docs.spec.ts rename to code/e2e-sandbox/addon-docs.spec.ts diff --git a/code/e2e-tests/addon-mcp.spec.ts b/code/e2e-sandbox/addon-mcp.spec.ts similarity index 100% rename from code/e2e-tests/addon-mcp.spec.ts rename to code/e2e-sandbox/addon-mcp.spec.ts diff --git a/code/e2e-tests/addon-onboarding.spec.ts b/code/e2e-sandbox/addon-onboarding.spec.ts similarity index 100% rename from code/e2e-tests/addon-onboarding.spec.ts rename to code/e2e-sandbox/addon-onboarding.spec.ts diff --git a/code/e2e-tests/addon-toolbars.spec.ts b/code/e2e-sandbox/addon-toolbars.spec.ts similarity index 100% rename from code/e2e-tests/addon-toolbars.spec.ts rename to code/e2e-sandbox/addon-toolbars.spec.ts diff --git a/code/e2e-tests/addon-viewport.spec.ts b/code/e2e-sandbox/addon-viewport.spec.ts similarity index 100% rename from code/e2e-tests/addon-viewport.spec.ts rename to code/e2e-sandbox/addon-viewport.spec.ts diff --git a/code/e2e-tests/change-detection.spec.ts b/code/e2e-sandbox/change-detection.spec.ts similarity index 100% rename from code/e2e-tests/change-detection.spec.ts rename to code/e2e-sandbox/change-detection.spec.ts diff --git a/code/e2e-tests/component-tests.spec.ts b/code/e2e-sandbox/component-tests.spec.ts similarity index 100% rename from code/e2e-tests/component-tests.spec.ts rename to code/e2e-sandbox/component-tests.spec.ts diff --git a/code/e2e-tests/framework-nextjs.spec.ts b/code/e2e-sandbox/framework-nextjs.spec.ts similarity index 100% rename from code/e2e-tests/framework-nextjs.spec.ts rename to code/e2e-sandbox/framework-nextjs.spec.ts diff --git a/code/e2e-tests/framework-svelte.spec.ts b/code/e2e-sandbox/framework-svelte.spec.ts similarity index 100% rename from code/e2e-tests/framework-svelte.spec.ts rename to code/e2e-sandbox/framework-svelte.spec.ts diff --git a/code/e2e-tests/framework-vue3.spec.ts b/code/e2e-sandbox/framework-vue3.spec.ts similarity index 100% rename from code/e2e-tests/framework-vue3.spec.ts rename to code/e2e-sandbox/framework-vue3.spec.ts diff --git a/code/e2e-tests/json-files.spec.ts b/code/e2e-sandbox/json-files.spec.ts similarity index 100% rename from code/e2e-tests/json-files.spec.ts rename to code/e2e-sandbox/json-files.spec.ts diff --git a/code/e2e-tests/manager.spec.ts b/code/e2e-sandbox/manager.spec.ts similarity index 100% rename from code/e2e-tests/manager.spec.ts rename to code/e2e-sandbox/manager.spec.ts diff --git a/code/e2e-tests/module-mocking.spec.ts b/code/e2e-sandbox/module-mocking.spec.ts similarity index 100% rename from code/e2e-tests/module-mocking.spec.ts rename to code/e2e-sandbox/module-mocking.spec.ts diff --git a/code/e2e-tests/navigation.spec.ts b/code/e2e-sandbox/navigation.spec.ts similarity index 100% rename from code/e2e-tests/navigation.spec.ts rename to code/e2e-sandbox/navigation.spec.ts diff --git a/code/e2e-tests/preview-api.spec.ts b/code/e2e-sandbox/preview-api.spec.ts similarity index 100% rename from code/e2e-tests/preview-api.spec.ts rename to code/e2e-sandbox/preview-api.spec.ts diff --git a/code/e2e-tests/sb-module-mocking.spec.ts b/code/e2e-sandbox/sb-module-mocking.spec.ts similarity index 100% rename from code/e2e-tests/sb-module-mocking.spec.ts rename to code/e2e-sandbox/sb-module-mocking.spec.ts diff --git a/code/e2e-tests/storybook-hooks.spec.ts b/code/e2e-sandbox/storybook-hooks.spec.ts similarity index 100% rename from code/e2e-tests/storybook-hooks.spec.ts rename to code/e2e-sandbox/storybook-hooks.spec.ts diff --git a/code/e2e-tests/storybook.setup.ts b/code/e2e-sandbox/storybook.setup.ts similarity index 100% rename from code/e2e-tests/storybook.setup.ts rename to code/e2e-sandbox/storybook.setup.ts diff --git a/code/e2e-tests/tags.spec.ts b/code/e2e-sandbox/tags.spec.ts similarity index 100% rename from code/e2e-tests/tags.spec.ts rename to code/e2e-sandbox/tags.spec.ts diff --git a/code/e2e-tests/util.ts b/code/e2e-sandbox/util.ts similarity index 100% rename from code/e2e-tests/util.ts rename to code/e2e-sandbox/util.ts diff --git a/code/playwright.config.ts b/code/playwright.config.ts index 6a9bacdeca82..262dba03cf5e 100644 --- a/code/playwright.config.ts +++ b/code/playwright.config.ts @@ -9,7 +9,7 @@ import { defineConfig, devices } from '@playwright/test'; /** See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './e2e-tests', + testDir: './e2e-sandbox', /* Maximum time one test can run for. */ timeout: 30 * 1000, expect: { @@ -61,6 +61,7 @@ export default defineConfig({ }, { name: 'chromium', + testIgnore: /.*\.setup\.ts/, dependencies: ['setup'], use: { ...devices['Desktop Chrome'], diff --git a/scripts/ci/common-jobs.ts b/scripts/ci/common-jobs.ts index e21d81e3ce7a..6512999ecca0 100644 --- a/scripts/ci/common-jobs.ts +++ b/scripts/ci/common-jobs.ts @@ -2,7 +2,7 @@ import glob from 'fast-glob'; import { join } from 'path/posix'; -import { WINDOWS_ROOT_DIR, WORKING_DIR } from './utils/constants.ts'; +import { LINUX_ROOT_DIR, WINDOWS_ROOT_DIR, WORKING_DIR } from './utils/constants.ts'; import { CACHE_KEYS, CACHE_PATHS, @@ -161,6 +161,42 @@ export const storybookChromatic = defineJob( [commonJobsNoOpJob] ); +export const internalStorybookE2e = defineJob( + 'Internal storybook E2E', + (workflowName) => ({ + executor: { + name: 'sb_playwright', + class: 'medium+', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Run internal Storybook', + working_directory: 'code', + background: true, + command: 'yarn storybook:ui', + }, + }, + server.wait(['6006']), + { + run: { + name: 'Run internal Storybook E2E tests', + command: 'yarn task e2e-tests-internal --no-link -s e2e-tests-internal --junit', + }, + }, + artifact.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results'), 'test-results'), + artifact.persist( + join(LINUX_ROOT_DIR, WORKING_DIR, 'code', 'playwright-results'), + 'playwright-results' + ), + testResults.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results')), + ...workflow.reportOnFailure(workflowName), + ], + }), + [commonJobsNoOpJob] +); + export const check = defineJob( 'TypeScript validation', (workflowName) => ({ @@ -253,7 +289,7 @@ export const testsUnit_linux = defineJob( run: { name: 'Run tests', command: [ - 'TEST_FILES=$(circleci tests glob "code/**/*.{test,spec}.{ts,tsx,js,jsx,cjs}" "scripts/**/*.{test,spec}.{ts,tsx,js,jsx,cjs}" | sed "/e2e-tests\\//d" | sed "/node_modules\\//d")', + 'TEST_FILES=$(circleci tests glob "code/**/*.{test,spec}.{ts,tsx,js,jsx,cjs}" "scripts/**/*.{test,spec}.{ts,tsx,js,jsx,cjs}" | sed "/e2e-sandbox\\//d" | sed "/e2e-internal\\//d" | sed "/node_modules\\//d")', 'echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=./test-results/junit.xml" --verbose', ].join('\n'), }, @@ -281,7 +317,7 @@ export const testsStories_linux = defineJob( run: { name: 'Run stories tests', command: [ - 'TEST_FILES=$(circleci tests glob "code/**/*.{stories}.{ts,tsx,js,jsx,cjs}" | sed "/e2e-tests\\//d" | sed "/node_modules\\//d")', + 'TEST_FILES=$(circleci tests glob "code/**/*.{stories}.{ts,tsx,js,jsx,cjs}" | sed "/e2e-sandbox\\//d" | sed "/e2e-internal\\//d" | sed "/node_modules\\//d")', 'echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=./test-results/junit.xml" --verbose', ].join('\n'), }, diff --git a/scripts/ci/main.ts b/scripts/ci/main.ts index acf3346131d2..21780c03bb96 100644 --- a/scripts/ci/main.ts +++ b/scripts/ci/main.ts @@ -13,6 +13,7 @@ import { knip, lint, fmt, + internalStorybookE2e, storybookChromatic, testUnit_windows, testsStories_linux, @@ -66,6 +67,7 @@ function generateConfig(workflow: Workflow) { knip, storybookChromatic, + internalStorybookE2e, benchmarkPackages, sandboxesNoOpJob, diff --git a/scripts/ci/sandboxes.ts b/scripts/ci/sandboxes.ts index fe17059bf8f9..4348cfd9801e 100644 --- a/scripts/ci/sandboxes.ts +++ b/scripts/ci/sandboxes.ts @@ -109,7 +109,7 @@ function defineSandboxJob_dev({ run: { name: 'Running E2E Tests', command: [ - 'TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}")', + 'TEST_FILES=$(circleci tests glob "code/e2e-sandbox/*.{test,spec}.{ts,js,mjs}")', `echo "$TEST_FILES" | circleci tests run --command="xargs yarn task e2e-tests-dev --template ${template} --no-link -s e2e-tests-dev --junit" --verbose --index=0 --total=1`, ].join('\n'), }, @@ -308,7 +308,7 @@ export function defineSandboxFlow(key: Key) { run: { name: 'Running E2E Tests', command: [ - `TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}")`, + `TEST_FILES=$(circleci tests glob "code/e2e-sandbox/*.{test,spec}.{ts,js,mjs}")`, `echo "$TEST_FILES" | circleci tests run --command="xargs yarn task e2e-tests --template ${key} --no-link -s e2e-tests --junit" --verbose --index=0 --total=1`, ].join('\n'), }, diff --git a/scripts/task.ts b/scripts/task.ts index 02df2448d8ef..3987f2390101 100644 --- a/scripts/task.ts +++ b/scripts/task.ts @@ -23,6 +23,7 @@ import { compile } from './tasks/compile.ts'; import { dev } from './tasks/dev.ts'; import { e2eTestsBuild } from './tasks/e2e-tests-build.ts'; import { e2eTestsDev } from './tasks/e2e-tests-dev.ts'; +import { e2eTestsInternal } from './tasks/e2e-tests-internal.ts'; import { generate } from './tasks/generate.ts'; import { install } from './tasks/install.ts'; import { publish } from './tasks/publish.ts'; @@ -100,13 +101,22 @@ export const tasks = { chromatic, 'e2e-tests': e2eTestsBuild, 'e2e-tests-dev': e2eTestsDev, + 'e2e-tests-internal': e2eTestsInternal, bench, 'vitest-integration': vitestTests, }; export type TaskKey = keyof typeof tasks; function isSandboxTask(taskKey: TaskKey) { - return !['install', 'compile', 'publish', 'run-registry', 'check', 'sync-docs'].includes(taskKey); + return ![ + 'install', + 'compile', + 'publish', + 'run-registry', + 'check', + 'sync-docs', + 'e2e-tests-internal', + ].includes(taskKey); } export const options = createOptions({ diff --git a/scripts/tasks/e2e-tests-internal.ts b/scripts/tasks/e2e-tests-internal.ts new file mode 100644 index 000000000000..b7a74bab82cb --- /dev/null +++ b/scripts/tasks/e2e-tests-internal.ts @@ -0,0 +1,43 @@ +import waitOn from 'wait-on'; + +import type { Task } from '../task.ts'; +import { exec } from '../utils/exec.ts'; + +const STORYBOOK_PORT = 6006; +const READY_TIMEOUT_MS = 25_000; + +export const e2eTestsInternal: Task = { + description: 'Run e2e tests against the internal Storybook UI (code/.storybook)', + dependsOn: ['compile'], + junit: true, + async ready() { + return false; + }, + async run({ codeDir, junitFilename }, { dryRun, debug }) { + const storybookUrl = `http://localhost:${STORYBOOK_PORT}`; + const env = { + CI: 'true', + STORYBOOK_URL: storybookUrl, + ...(junitFilename && { + PLAYWRIGHT_JUNIT_OUTPUT_NAME: junitFilename, + }), + }; + + if (!dryRun) { + await waitOn({ + resources: [`${storybookUrl}/index.json`], + interval: 16, + timeout: READY_TIMEOUT_MS, + }); + } + + await exec( + 'yarn playwright test -c e2e-internal/playwright.config.ts', + { + env, + cwd: codeDir, + }, + { dryRun, debug } + ); + }, +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts index 6a47e2cdea2f..6998b3c7793c 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { expect, test } from "@playwright/test"; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from "../../../../code/e2e-sandbox/util"; const STORYBOOK_URL = "http://localhost:6006"; const TEST_STORY_PATH = path.resolve( diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts index 242823cd4a0e..55be83eeeae8 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { SbPage } from '../../../../code/e2e-tests/util'; +import { SbPage } from '../../../../code/e2e-sandbox/util'; const STORYBOOK_URL = 'http://localhost:6006'; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts index 0e093e00d614..6ceb1d0e89f5 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import process from "process"; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from "../../../../code/e2e-sandbox/util"; const STORYBOOK_URL = "http://localhost:6006"; const type = process.env.STORYBOOK_TYPE || "dev"; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts index a98bab455e37..d734f4b8c022 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { expect, test, type Page } from "@playwright/test"; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from "../../../../code/e2e-sandbox/util"; const STORYBOOK_URL = "http://localhost:6006"; const TEST_STORY_PATH = path.resolve( diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/composition.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/composition.spec.ts index 95eba0ca9b7e..de55450eeb25 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/composition.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/composition.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { SbPage } from '../../../../code/e2e-tests/util'; +import { SbPage } from '../../../../code/e2e-sandbox/util'; const STORYBOOK_URL = 'http://localhost:6006'; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts index 0e093e00d614..6ceb1d0e89f5 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import process from "process"; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from "../../../../code/e2e-sandbox/util"; const STORYBOOK_URL = "http://localhost:6006"; const type = process.env.STORYBOOK_TYPE || "dev";