From 8d1a43f907f89ba043a580b97b5a272af46018a2 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 18 Sep 2026 18:00:28 +0100 Subject: [PATCH 1/3] Align npm scripts with the other web apps and pin the dev port The four web apps had drifted apart on script names, and three of them served on port 3000, so only one could run at a time. This app keeps 3000 because it matches the CORS configuration; classroom, data and ml-trainer move to 3001-3003. Preview gets 4000. strictPort makes a clash fail rather than quietly move to the next free port. That matters because playwright.config.ts reuses an existing server on the dev port, so a silent fallback would point one app's e2e run at a sibling app. The e2e page objects build absolute URLs from E2E_PORT, whose default only worked because the dev and CI preview ports were both 3000. Splitting them breaks that, so playwright.config.ts now derives both its webServer and E2E_PORT from a single constant. Scripts: dev replaces start, preview replaces the hand-rolled serve, format and format:check are new, invalidate uses the shared invalidate-cloudfront-distribution rather than a raw aws call, and prepare becomes postinstall. ci runs typecheck, lint, test, i18n:tidy then build, matching the ui repo. Drop the npx serve and curl steps from the build workflow. They predate the move to Playwright, which starts its own server inside the test container, so nothing was reaching them. Exclude the generated typeshed JSON that the stubs script copies out of node_modules from prettier, mirroring how ml-trainer excludes its generated messages. That leaves one real formatting fix in chunk-util.ts. --- .github/workflows/build.yml | 4 ---- .prettierignore | 1 + README.md | 6 +++--- package.json | 32 +++++++++++++++++--------------- playwright.config.ts | 14 ++++++++++---- src/common/chunk-util.ts | 2 +- vite.config.ts | 5 +++++ 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f98f29c9..eb8dce901 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,10 +60,6 @@ jobs: - run: npm run ci env: VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - - run: mkdir -p /tmp/app${BASE_URL} && cp -r build/* /tmp/app${BASE_URL} && npx serve --no-clipboard -l 3000 /tmp/app & - if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' - - run: curl --insecure -4 --retry 7 --retry-connrefused http://localhost:3000 1>/dev/null - if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' - name: Run Playwright tests if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' uses: docker://mcr.microsoft.com/playwright:v1.63.0-noble diff --git a/.prettierignore b/.prettierignore index e8734ec6c..051b4f585 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ src/editor/codemirror/lint/lint.ts styled-system src/styled-system.css +src/micropython/main/typeshed.*.json diff --git a/README.md b/README.md index 5994c9d56..0782aba0d 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ Getting up and running: 1. Ensure you have a working [Node.js environment](https://nodejs.org/en/download/). We recommend using the LTS version of Node and NPM version 8 or newer. 2. Checkout this repository with Git. GitHub have some [learning resources for Git](https://docs.github.com/en/get-started/quickstart/git-and-github-learning-resources) that you may find useful. 3. Install the dependencies by running `npm install` on the command line in the checkout folder. -4. Choose from the NPM scripts documented below. Try `npm start` if you're not sure. +4. Choose from the NPM scripts documented below. Try `npm run dev` if you're not sure. -### `npm start` +### `npm run dev` Runs the app in the development mode. @@ -52,7 +52,7 @@ Launches the test runner in the interactive watch mode running the end to end te These are excluded from the normal test run. -The tests expect the app to already be running on http://localhost:3000, for example via `npm start`. +The tests expect the app to already be running on http://localhost:3000, for example via `npm run dev`. We use [Playwright](https://playwright.dev/). diff --git a/package.json b/package.json index 274303be8..f28b67b55 100644 --- a/package.json +++ b/package.json @@ -85,28 +85,30 @@ "npm": ">=11.18.0" }, "scripts": { + "dev": "vite dev", + "predev": "npm run generate", "build": "vite build", "prebuild": "npm run generate", - "ci": "npm run typecheck && npm run test && npm run lint && npm run i18n:tidy -- --check && npm run build", - "deploy": "website-deploy-aws", - "dev:link-ui": "rm -rf node_modules/@microbit/ui node_modules/@microbit/ui-patterns node_modules/@microbit/i18n-tools && ln -s ../../../ui/packages/ui node_modules/@microbit/ui && ln -s ../../../ui/packages/ui-patterns node_modules/@microbit/ui-patterns && ln -s ../../../ui/packages/i18n-tools node_modules/@microbit/i18n-tools && ln -sf ../@microbit/i18n-tools/bin/microbit-i18n.mjs node_modules/.bin/microbit-i18n && rm -rf styled-system node_modules/.vite && npm run panda", - "generate": "npm run panda && npm run i18n:compile && npm run stubs", - "i18n:compile": "microbit-i18n compile", - "invalidate": "aws cloudfront create-invalidation --distribution-id $(printenv ${STAGE}_CLOUDFRONT_DISTRIBUTION_ID) --paths \"/*\"", - "lint": "eslint . --max-warnings 0", - "prepare": "npm run generate", + "preview": "vite preview", "panda": "panda codegen", "panda:watch": "panda codegen --watch", - "serve": "npx serve --no-clipboard -l 3000 -- build/", - "prestart": "npm run generate", - "start": "vite dev", + "generate": "npm run panda && npm run i18n:compile && npm run stubs", "stubs": "cp node_modules/@microbit/micropython-microbit-stubs/typeshed.*.json src/micropython/main/", - "test:e2e:headless": "playwright test", - "test:e2e": "playwright test --ui", - "test": "vitest", - "pretypecheck": "npm run generate", + "postinstall": "npm run generate", + "lint": "eslint . --max-warnings 0", + "format": "prettier --write src", + "format:check": "prettier --check src", "typecheck": "tsc --noEmit", + "pretypecheck": "npm run generate", "typecheck:watch": "tsc --noEmit --watch", + "test": "vitest", + "test:e2e": "playwright test --ui", + "test:e2e:headless": "playwright test", + "ci": "npm run typecheck && npm run lint && npm run test && npm run i18n:tidy -- --check && npm run build", + "deploy": "website-deploy-aws", + "invalidate": "invalidate-cloudfront-distribution", + "dev:link-ui": "rm -rf node_modules/@microbit/ui node_modules/@microbit/ui-patterns node_modules/@microbit/i18n-tools && ln -s ../../../ui/packages/ui node_modules/@microbit/ui && ln -s ../../../ui/packages/ui-patterns node_modules/@microbit/ui-patterns && ln -s ../../../ui/packages/i18n-tools node_modules/@microbit/i18n-tools && ln -sf ../@microbit/i18n-tools/bin/microbit-i18n.mjs node_modules/.bin/microbit-i18n && rm -rf styled-system node_modules/.vite && npm run panda", + "i18n:compile": "microbit-i18n compile", "i18n:tidy": "microbit-i18n tidy", "i18n:download": "microbit-i18n download", "i18n:upload": "microbit-i18n upload", diff --git a/playwright.config.ts b/playwright.config.ts index 2a672411f..6454be1bd 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,11 @@ import { defineConfig, devices } from "@playwright/test"; +// The page objects build their absolute URLs from E2E_PORT, so it has to +// name whichever server this run starts: the dev server locally, or +// `vite preview` on the production build in CI. +const port = process.env.CI ? 4000 : 3000; +process.env.E2E_PORT ??= String(port); + /** * See https://playwright.dev/docs/test-configuration. */ @@ -35,12 +41,12 @@ export default defineConfig({ webServer: { ...(process.env.CI ? { - command: `npx vite preview --port 3000 --base ${process.env.BASE_URL}`, - url: `http://localhost:3000${process.env.BASE_URL}`, + command: `npx vite preview --port ${port} --base ${process.env.BASE_URL}`, + url: `http://localhost:${port}${process.env.BASE_URL}`, } : { - command: "npm run start", - url: "http://localhost:3000", + command: "npm run dev", + url: `http://localhost:${port}`, }), reuseExistingServer: !process.env.CI, }, diff --git a/src/common/chunk-util.ts b/src/common/chunk-util.ts index 21c13da11..8bb416654 100644 --- a/src/common/chunk-util.ts +++ b/src/common/chunk-util.ts @@ -14,7 +14,7 @@ export const retryAsyncLoad = async ( ): Promise => { let waitTime = 250; let attempts = 0; - + while (true) { try { // Must await here! diff --git a/vite.config.ts b/vite.config.ts index d40ff7839..db42bcc2b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -110,6 +110,11 @@ export default defineConfig(({ mode }) => { }, server: { port: 3000, + strictPort: true, + }, + preview: { + port: 4000, + strictPort: true, }, assetsInclude: ["**/*.hex"], plugins: [ From 4e97e426e7c7feda97b93ab579047b141db8cd35 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 18 Sep 2026 18:06:40 +0100 Subject: [PATCH 2/3] Correct the end-to-end test docs The README said the app had to be running already. That dates from 2021 and predates the move to Playwright, whose webServer starts the dev server and reuses one already on the port. Also describe test:e2e as the Playwright UI rather than a watch mode, mention test:e2e:headless, and note that the browsers are a separate download. --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0782aba0d..caecce7bc 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,16 @@ If you have a connected micro:bit device, then setting the environment variable ### `npm run test:e2e` -Launches the test runner in the interactive watch mode running the end to end tests. +Opens the [Playwright](https://playwright.dev/) UI to run the end to end tests. +`npm run test:e2e:headless` runs them without it. These are excluded from the normal test run. -The tests expect the app to already be running on http://localhost:3000, for example via `npm run dev`. +Playwright starts the dev server itself, reusing one already running on +http://localhost:3000 if there is one. -We use [Playwright](https://playwright.dev/). +The browsers are a separate download: run `npx playwright install` before the +first run. The CI tests run these end-to-end tests against a production build. From 0a45a2dc2c5ad10b72ec4a48be2ea535544ae777 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Sat, 19 Sep 2026 12:00:12 +0100 Subject: [PATCH 3/3] Capture the Playwright report when the end-to-end tests fail The upload step pointed at reports/, but Playwright writes playwright-report/, so it never captured anything. The e2e step also had no timeout of its own, so a slow run took the whole job past its limit and the run was cancelled rather than failed, which skipped the upload too. Bound the step and leave the job room for the deploy that follows it. --- .github/workflows/build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb8dce901..cb8f7f126 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ env: jobs: build: - timeout-minutes: 15 + timeout-minutes: 20 runs-on: ubuntu-latest permissions: contents: read @@ -62,6 +62,7 @@ jobs: VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - name: Run Playwright tests if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' + timeout-minutes: 10 uses: docker://mcr.microsoft.com/playwright:v1.63.0-noble with: args: npx playwright test @@ -69,8 +70,8 @@ jobs: if: (env.STAGE == 'REVIEW' || env.STAGE == 'STAGING') && failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: reports - path: reports/ + name: playwright-report + path: playwright-report/ retention-days: 3 - run: npm run deploy if: github.repository_owner == 'microbit-foundation' && (env.STAGE == 'REVIEW' || success())