diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 00000000..60bbe9e1 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,50 @@ +name: E2E Tests + +on: + pull_request: + workflow_dispatch: + +jobs: + e2e: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare + uses: ./.github/actions/prepare + + - name: Setup E2E Environment + uses: ./.github/actions/setup-e2e-env + + - name: Install Templates Dependencies + run: ./scripts/install-deps + + - name: Run tests + run: | + juno dev start --headless & + ./scripts/e2e-starter e2e:ci + + - name: Upload Playwright report on failure + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 3 + + - name: Upload Playwright results on failure + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: test-results + path: test-results/ + retention-days: 3 + + may-merge: + needs: ['e2e'] + runs-on: ubuntu-latest + steps: + - name: Cleared for merging + run: echo OK diff --git a/.gitignore b/.gitignore index 6fd7d3c0..aaa783dd 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,10 @@ target/ out/ -templates/**/package-lock.json \ No newline at end of file +templates/**/package-lock.json + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..94de26a8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + juno-satellite: + image: junobuild/satellite:latest + ports: + - 4943:5987 + - 5999:5999 + volumes: + - juno_satellite:/juno/.juno + - ./juno.dev.config.js:/juno/juno.dev.config.js + - ./target/deploy:/juno/target/deploy/ + +volumes: + juno_satellite: diff --git a/e2e/__screenshots__/angular-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/angular-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..cf100898 Binary files /dev/null and b/e2e/__screenshots__/angular-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/angular-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/angular-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..0d050141 Binary files /dev/null and b/e2e/__screenshots__/angular-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/angular-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/angular-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..fc8e0134 Binary files /dev/null and b/e2e/__screenshots__/angular-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/angular-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/angular-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..a597e91e Binary files /dev/null and b/e2e/__screenshots__/angular-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/astro-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/astro-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..ff150aea Binary files /dev/null and b/e2e/__screenshots__/astro-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/astro-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/astro-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..9f99141c Binary files /dev/null and b/e2e/__screenshots__/astro-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/astro-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/astro-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..c9ed559c Binary files /dev/null and b/e2e/__screenshots__/astro-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/astro-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/astro-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..93b2e02e Binary files /dev/null and b/e2e/__screenshots__/astro-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/nextjs-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/nextjs-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..f01f6169 Binary files /dev/null and b/e2e/__screenshots__/nextjs-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/nextjs-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/nextjs-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..64db3f84 Binary files /dev/null and b/e2e/__screenshots__/nextjs-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/nextjs-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/nextjs-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..16b8406e Binary files /dev/null and b/e2e/__screenshots__/nextjs-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/nextjs-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/nextjs-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..aa271472 Binary files /dev/null and b/e2e/__screenshots__/nextjs-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/react-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/react-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..118f4087 Binary files /dev/null and b/e2e/__screenshots__/react-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/react-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/react-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..70d0be9f Binary files /dev/null and b/e2e/__screenshots__/react-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/react-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/react-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..a86a9fa9 Binary files /dev/null and b/e2e/__screenshots__/react-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/react-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/react-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..a95ef71e Binary files /dev/null and b/e2e/__screenshots__/react-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/react-ts-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/react-ts-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..118f4087 Binary files /dev/null and b/e2e/__screenshots__/react-ts-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/react-ts-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/react-ts-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..70d0be9f Binary files /dev/null and b/e2e/__screenshots__/react-ts-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/react-ts-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/react-ts-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..a86a9fa9 Binary files /dev/null and b/e2e/__screenshots__/react-ts-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/react-ts-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/react-ts-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..a95ef71e Binary files /dev/null and b/e2e/__screenshots__/react-ts-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/sveltekit-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/sveltekit-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..8efb9297 Binary files /dev/null and b/e2e/__screenshots__/sveltekit-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/sveltekit-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/sveltekit-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..9f99141c Binary files /dev/null and b/e2e/__screenshots__/sveltekit-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/sveltekit-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/sveltekit-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..d4af1632 Binary files /dev/null and b/e2e/__screenshots__/sveltekit-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/sveltekit-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/sveltekit-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..93b2e02e Binary files /dev/null and b/e2e/__screenshots__/sveltekit-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/vue-starter/development/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/vue-starter/development/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..8efb9297 Binary files /dev/null and b/e2e/__screenshots__/vue-starter/development/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/vue-starter/development/starter.spec.ts/light-mode.png b/e2e/__screenshots__/vue-starter/development/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..9f99141c Binary files /dev/null and b/e2e/__screenshots__/vue-starter/development/starter.spec.ts/light-mode.png differ diff --git a/e2e/__screenshots__/vue-starter/production/starter.spec.ts/dark-mode.png b/e2e/__screenshots__/vue-starter/production/starter.spec.ts/dark-mode.png new file mode 100644 index 00000000..d4af1632 Binary files /dev/null and b/e2e/__screenshots__/vue-starter/production/starter.spec.ts/dark-mode.png differ diff --git a/e2e/__screenshots__/vue-starter/production/starter.spec.ts/light-mode.png b/e2e/__screenshots__/vue-starter/production/starter.spec.ts/light-mode.png new file mode 100644 index 00000000..1c173412 Binary files /dev/null and b/e2e/__screenshots__/vue-starter/production/starter.spec.ts/light-mode.png differ diff --git a/e2e/starter.spec.ts b/e2e/starter.spec.ts new file mode 100644 index 00000000..8d0a7394 --- /dev/null +++ b/e2e/starter.spec.ts @@ -0,0 +1,72 @@ +import {expect, test} from '@playwright/test'; + +const TEMPLATE = process.env.TEMPLATE ?? ''; + +(['light', 'dark'] as const).forEach((mode) => { + test.describe(`${mode} mode`, () => { + test.use({colorScheme: mode}); + + test('match screenshot', async ({page}) => { + await page.goto('/'); + + await expect(page.getByText('Welcome to Juno')).toBeVisible(); + + await expect(page).toHaveScreenshot(`${mode}-mode.png`, {fullPage: true}); + }); + }); +}); + +test('has title', async ({page}) => { + await page.goto('/'); + + const capitalize = (s: string): string => s[0].toUpperCase() + s.slice(1); + const mapTemplateToTitle = (s: string): string => + s + .replaceAll('-ts-', '-') + .replaceAll('kit', 'Kit') + .replaceAll('nextjs', 'next.js') + .replaceAll('-', ' ') + .split(' ') + .map(capitalize) + .join(' '); + + await expect(page).toHaveTitle(`Juno / ${mapTemplateToTitle(TEMPLATE)}`); +}); + +test('get quickstart link', async ({page}) => { + await page.goto('/'); + + const link = page.locator('a[aria-label="Open quickstart guides on Juno\'s website"]'); + + const framework = TEMPLATE.split('-')[0]; + + await expect(link).toHaveAttribute('href', `https://juno.build/docs/guides/${framework}`); +}); + +test('get documentation link', async ({page}) => { + await page.goto('/'); + + const link = page.locator( + 'a[aria-label="Open the list of features for building apps on Juno\'s website"]' + ); + + await expect(link).toHaveAttribute('href', 'https://juno.build/docs/category/build'); +}); + +test('get ci link', async ({page}) => { + await page.goto('/'); + + const link = page.locator('a[aria-label="Open the guide to setting up GitHub Actions for Juno"]'); + + await expect(link).toHaveAttribute('href', 'https://juno.build/docs/guides/github-actions'); +}); + +test('get discord link', async ({page}) => { + await page.goto('/'); + + const link = page.locator( + 'a[aria-label="Join Juno\'s Discord channel for questions or to share the fun"]' + ); + + await expect(link).toHaveAttribute('href', 'https://discord.gg/wHZ57Z2RAG'); +}); diff --git a/juno.dev.config.js b/juno.dev.config.js new file mode 100644 index 00000000..a985e4dc --- /dev/null +++ b/juno.dev.config.js @@ -0,0 +1,11 @@ +import {defineDevConfig} from '@junobuild/config'; + +/** @type {import('@junobuild/config').JunoDevConfig} */ +export default defineDevConfig(() => ({ + satellite: { + collections: { + datastore: [], + storage: [] + } + } +})); diff --git a/package-lock.json b/package-lock.json index 298e2ede..07e7d482 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.20.0", "@junobuild/config": "^0.1.0", + "@playwright/test": "^1.51.1", "@types/node": "^22.13.4", "@types/prompts": "^2.4.9", "@types/tar-stream": "^3.1.3", @@ -879,6 +880,22 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz", + "integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.51.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -3071,6 +3088,21 @@ "node": ">=12.20.0" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4821,6 +4853,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz", + "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.51.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz", + "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", diff --git a/package.json b/package.json index d2f7da46..59ed58c4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,11 @@ "build": "tsc --noEmit && node rmdir.mjs && node esbuild.mjs", "dev": "node rmdir.mjs && NODE_ENV=development node esbuild.mjs", "lint": "eslint --max-warnings 0 \"src/**/*\"", - "update:juno": "./scripts/update-juno" + "update:juno": "./scripts/update-juno", + "e2e": "NODE_ENV=development playwright test", + "e2e:snapshots": "NODE_ENV=development playwright test --update-snapshots --reporter=list", + "e2e:ci": "playwright test --reporter=html", + "e2e:ci:snapshots": "playwright test --update-snapshots --reporter=html" }, "repository": { "type": "git", @@ -42,6 +46,7 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.20.0", "@junobuild/config": "^0.1.0", + "@playwright/test": "^1.51.1", "@types/node": "^22.13.4", "@types/prompts": "^2.4.9", "@types/tar-stream": "^3.1.3", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..f409b6aa --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,40 @@ +import {defineConfig, devices} from '@playwright/test'; + +const DEV = (process.env.NODE_ENV ?? 'production') === 'development'; +const TEMPLATE = process.env.TEMPLATE ?? 'test'; + +const PORT = TEMPLATE.includes('angular') + ? 4200 + : TEMPLATE.includes('astro') + ? 4321 + : TEMPLATE.includes('next') + ? 3000 + : 5173; + +export default defineConfig({ + webServer: [ + { + command: `npm run dev --prefix templates/${TEMPLATE}`, + reuseExistingServer: true, + port: PORT + } + ], + testDir: 'e2e', + testMatch: ['**/*.e2e.ts', '**/*.spec.ts'], + timeout: 60000, + use: { + testIdAttribute: 'data-tid', + trace: 'on', + ...(DEV && {headless: false}), + screenshot: 'only-on-failure', + baseURL: `http://localhost:${PORT}` + }, + projects: [ + { + name: 'Google Chrome', + use: {...devices['Desktop Chrome'], channel: 'chrome'} + } + ], + workers: process.env.CI ? 1 : undefined, + snapshotPathTemplate: `{testDir}/__screenshots__/${TEMPLATE}/${process.env.NODE_ENV ?? 'production'}/{testFilePath}/{arg}{ext}` +}); diff --git a/scripts/e2e-starter b/scripts/e2e-starter new file mode 100755 index 00000000..0d6f35aa --- /dev/null +++ b/scripts/e2e-starter @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +TEST_TYPE=${1:-e2e} + +function run_test() { + local template=$1 + + echo -e "\n***** $TEST_TYPE for $template *****\n" + + TEMPLATE=$template npm run "$TEST_TYPE" +} + +STARTERS=angular-starter,astro-starter,nextjs-starter,react-starter,react-ts-starter,sveltekit-starter,vue-starter + +for template in $(echo $STARTERS | sed "s/,/ /g"); do + run_test "$template" +done \ No newline at end of file diff --git a/scripts/install-deps b/scripts/install-deps new file mode 100755 index 00000000..d3fedd1c --- /dev/null +++ b/scripts/install-deps @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +function install_deps() { + local template=$1 + + echo -e "\n***** Install dependencies for $template *****\n" + + npm i --prefix "templates/$template" +} + +STARTERS=angular-starter,astro-starter,nextjs-starter,react-starter,react-ts-starter,sveltekit-starter,vue-starter + +for template in $(echo $STARTERS | sed "s/,/ /g"); do + install_deps "$template" +done \ No newline at end of file