-
Notifications
You must be signed in to change notification settings - Fork 666
feat(ci): add windows arm64 e2e #1653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
92bbce4
eb1df03
57394e6
08b2a99
15e1743
ee848ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| name: Windows ARM64 E2E | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | ||
|
|
||
| jobs: | ||
| windows-arm64-e2e: | ||
| runs-on: windows-11-arm | ||
| timeout-minutes: 120 | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e | ||
| with: | ||
| node-version: '24.14.1' | ||
| package-manager-cache: false | ||
|
|
||
| - name: Setup pnpm | ||
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install | ||
|
|
||
| - name: Configure pnpm workspace for Windows arm64 | ||
| run: pnpm run install:sharp | ||
| env: | ||
| TARGET_OS: win32 | ||
| TARGET_ARCH: arm64 | ||
|
|
||
| - name: Install Windows arm64 dependencies | ||
| run: pnpm install | ||
| env: | ||
| npm_config_build_from_source: true | ||
| npm_config_platform: win32 | ||
| npm_config_arch: arm64 | ||
|
|
||
| - name: Install Windows arm64 runtimes | ||
| run: pnpm run installRuntime:win:arm64 | ||
|
|
||
| - name: Build Windows arm64 package | ||
| run: | | ||
| pnpm run build | ||
| pnpm run plugin:bundle:clean | ||
| pnpm run plugin:bundle -- --name feishu --platform win32 --arch arm64 | ||
| pnpm exec electron-builder --win --arm64 --publish=never | ||
| env: | ||
| VITE_GITHUB_CLIENT_ID: ${{ secrets.DC_GITHUB_CLIENT_ID }} | ||
| VITE_GITHUB_CLIENT_SECRET: ${{ secrets.DC_GITHUB_CLIENT_SECRET }} | ||
| VITE_GITHUB_REDIRECT_URI: ${{ secrets.DC_GITHUB_REDIRECT_URI }} | ||
| VITE_PROVIDER_DB_URL: ${{ secrets.CDN_PROVIDER_DB_URL }} | ||
|
|
||
| - name: Verify bundled plugins | ||
| shell: bash | ||
| run: | | ||
| pnpm run plugin:verify -- --name feishu --platform win32 --arch arm64 --plugin-root dist/win-arm64-unpacked/resources/app.asar.unpacked/plugins | ||
|
|
||
| - name: Run packaged E2E smoke tests | ||
| run: pnpm run e2e:smoke:ci | ||
| env: | ||
| DEEPCHAT_E2E_APP_MODE: packaged | ||
| DEEPCHAT_E2E_EXECUTABLE_PATH: ${{ github.workspace }}/dist/win-arm64-unpacked/DeepChat.exe | ||
|
|
||
| - name: Upload Windows arm64 artifacts | ||
| if: always() | ||
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f | ||
| with: | ||
| name: deepchat-win-arm64-e2e-artifacts | ||
| if-no-files-found: warn | ||
| path: | | ||
| dist/* | ||
| !dist/win-unpacked | ||
| !dist/win-arm64-unpacked | ||
| test-results/e2e | ||
| playwright-report |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Windows ARM64 Support Plan | ||
|
|
||
| ## Architecture | ||
|
|
||
| - Validate a packaged/unpacked ARM64 build with a Windows ARM64 manual workflow running on GitHub's `windows-11-arm` runner and Playwright Electron smoke tests. | ||
| - Extend the manual build workflow's Windows matrix to produce `win-x64` and `win-arm64` artifacts while keeping the release workflow on Windows x64 only. | ||
| - Keep the Windows ARM64 runtime script explicit: install only verified native `uv`, `node`, and `ripgrep` artifacts. | ||
| - Provide a CI-specific E2E mode that runs only non-provider smoke specs against the runner profile. | ||
|
|
||
| ## E2E Data Flow | ||
|
|
||
| 1. The Playwright fixture launches DeepChat with the default Electron `userData` path for the current runner/user. | ||
| 2. CI Playwright config matches only launch and settings-navigation smoke specs. | ||
| 3. Chat, session persistence, and provider connectivity specs remain available for local/manual runs with configured providers. | ||
|
|
||
| ## Runtime Behavior | ||
|
|
||
| - `installRuntime:win:arm64` calls `tiny-runtime-injector` directly for `uv`, `node`, and `ripgrep`. | ||
| - `ripgrep` is pinned to `15.1.0` for Windows ARM64 because the injector default `14.1.1` has no ARM64 Windows release asset. | ||
| - `rtk` is intentionally omitted until upstream ships a Windows ARM64 release asset; existing runtime consumers continue to detect missing bundled binaries and fall back to system/runtime-unavailable behavior. | ||
|
|
||
| ## Validation | ||
|
|
||
| - Runtime fallback tests cover missing bundled runtime behavior. | ||
| - Existing RTK fallback coverage remains in place. | ||
| - Skill runtime tests cover the no-UV/no-system-Python auto-runtime failure path. | ||
| - The manual build workflow validates Windows x64 and Windows ARM64 artifact generation. | ||
| - The new manual workflow validates Windows ARM64 build, plugin bundle, app launch, route switching, and settings navigation. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Windows ARM64 Support Spec | ||
|
|
||
| ## User Story | ||
|
|
||
| DeepChat maintainers need a reliable way to validate Windows ARM64 builds without owning Windows ARM64 hardware, so the project can ship a Windows ARM64 package only after it passes smoke coverage on a real ARM64 Windows runner. | ||
|
|
||
| ## Acceptance Criteria | ||
|
|
||
| - A manual GitHub Actions workflow runs on `windows-11-arm` and builds the Windows ARM64 app. | ||
| - The workflow runs E2E smoke tests that do not require configured provider credentials. | ||
| - The E2E run uses the runner's default profile and validates launch, routing, and settings window behavior. | ||
| - The manual build workflow can produce both Windows x64 and Windows ARM64 artifacts. | ||
| - Windows ARM64 bundles only verified native runtimes: `uv`, `node`, and `ripgrep`. | ||
| - `rtk` is not bundled on Windows ARM64 until upstream provides a Windows ARM64 binary. | ||
| - Existing Windows x64, macOS, and Linux runtime install scripts remain strict. | ||
| - The Windows ARM64 workflow uploads build artifacts and E2E diagnostics. | ||
|
|
||
| ## Non-Goals | ||
|
|
||
| - Enable Windows ARM64 in the release workflow only after the manual Windows ARM64 E2E workflow has passed. | ||
| - Not every optional runtime is bundled on Windows ARM64. | ||
| - Provider-backed chat requests must not run in this CI workflow. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - Keep CI smoke coverage provider-independent; provider-backed specs remain local/manual only. | ||
| - Keep local `pnpm run e2e:smoke` behavior compatible with existing manual smoke tests. | ||
| - Keep runtime fallback behavior aligned with existing `RuntimeHelper`, RTK, and skill runtime logic. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Windows ARM64 Support Tasks | ||
|
|
||
| - [x] Add SDD spec, plan, and task tracking. | ||
| - [x] Verify Windows ARM64 runtime artifact availability. | ||
| - [x] Wire `installRuntime:win:arm64` to explicit `uv`, `node`, and `ripgrep` installation. | ||
| - [x] Add CI E2E support for non-provider smoke tests. | ||
| - [x] Keep E2E on the default runner profile. | ||
| - [x] Add packaged-app E2E launch mode. | ||
| - [x] Add Windows ARM64 manual GitHub Actions workflow. | ||
| - [x] Enable Windows ARM64 in the manual build workflow. | ||
| - [x] Add targeted unit coverage for runtime fallback paths. | ||
| - [ ] Enable Windows ARM64 in the release workflow after the manual workflow passes on GitHub. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,20 @@ import { | |
| type TestInfo | ||
| } from '@playwright/test' | ||
| import { existsSync } from 'node:fs' | ||
| import { arch } from 'node:os' | ||
| import { dirname, resolve } from 'node:path' | ||
| import { fileURLToPath } from 'node:url' | ||
|
|
||
| const FIXTURE_DIR = dirname(fileURLToPath(import.meta.url)) | ||
| const REPO_ROOT = resolve(FIXTURE_DIR, '..', '..', '..') | ||
| const BUILT_MAIN_ENTRY = resolve(REPO_ROOT, 'out', 'main', 'index.js') | ||
| const BUILT_RENDERER_ENTRY = resolve(REPO_ROOT, 'out', 'renderer', 'index.html') | ||
| const WINDOWS_PACKAGED_EXECUTABLE = resolve( | ||
| REPO_ROOT, | ||
| 'dist', | ||
| arch() === 'arm64' ? 'win-arm64-unpacked' : 'win-unpacked', | ||
| 'DeepChat.exe' | ||
| ) | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) | ||
|
|
||
|
|
@@ -59,6 +67,20 @@ type ElectronFixtures = { | |
| launchApp: () => Promise<ElectronAppInstance> | ||
| } | ||
|
|
||
| const resolvePackagedExecutable = (): string => { | ||
| if (process.env.DEEPCHAT_E2E_EXECUTABLE_PATH) { | ||
| return resolve(process.env.DEEPCHAT_E2E_EXECUTABLE_PATH) | ||
| } | ||
|
|
||
| if (process.platform === 'win32') { | ||
| return WINDOWS_PACKAGED_EXECUTABLE | ||
| } | ||
|
|
||
| throw new Error( | ||
| 'DEEPCHAT_E2E_APP_MODE=packaged requires DEEPCHAT_E2E_EXECUTABLE_PATH on this platform.' | ||
| ) | ||
| } | ||
|
|
||
| const attachDiagnostics = async ( | ||
| testInfo: TestInfo, | ||
| consoleLogs: string[], | ||
|
|
@@ -75,17 +97,31 @@ const attachDiagnostics = async ( | |
| }) | ||
| } | ||
|
|
||
| const ensureBuiltAppExists = (): void => { | ||
| const ensureLaunchTargetExists = (): void => { | ||
| if (process.env.DEEPCHAT_E2E_APP_MODE === 'packaged') { | ||
| const executablePath = resolvePackagedExecutable() | ||
| if (!existsSync(executablePath)) { | ||
| throw new Error(`Packaged app executable not found at ${executablePath}.`) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| if (!existsSync(BUILT_MAIN_ENTRY)) { | ||
| throw new Error( | ||
| `Built app entry not found at ${BUILT_MAIN_ENTRY}. Run "pnpm run build" before "pnpm run e2e:smoke".` | ||
| ) | ||
| } | ||
|
|
||
| if (!existsSync(BUILT_RENDERER_ENTRY)) { | ||
| throw new Error( | ||
| `Built renderer entry not found at ${BUILT_RENDERER_ENTRY}. Run "pnpm run build" before "pnpm run e2e:smoke".` | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| export const test = base.extend<ElectronFixtures>({ | ||
| launchApp: async ({}, use, testInfo) => { | ||
| ensureBuiltAppExists() | ||
| ensureLaunchTargetExists() | ||
|
|
||
| const consoleLogs: string[] = [] | ||
| const pageErrors: string[] = [] | ||
|
|
@@ -112,10 +148,19 @@ export const test = base.extend<ElectronFixtures>({ | |
| const launchApp = async (): Promise<ElectronAppInstance> => { | ||
| launchCount += 1 | ||
| const currentLaunch = launchCount | ||
| const packaged = process.env.DEEPCHAT_E2E_APP_MODE === 'packaged' | ||
|
|
||
| const electronApp = await electron.launch({ | ||
| args: ['.'], | ||
| ...(packaged | ||
| ? { | ||
| executablePath: resolvePackagedExecutable(), | ||
| args: [] | ||
| } | ||
| : { | ||
| args: ['.'] | ||
| }), | ||
| cwd: REPO_ROOT, | ||
| env: process.env, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restore isolated E2E user-data profile for launched app. Line 160 forwards the host environment unchanged, and this fixture writes onboarding/provider/model config. In packaged smoke runs, that can persist into a real/default DeepChat profile and cause state leakage/flaky reruns. Suggested fix+ const launchEnv: NodeJS.ProcessEnv = { ...process.env }
+ if (process.env.CI === 'true' || shouldUseMockProvider()) {
+ launchEnv.DEEPCHAT_E2E = '1'
+ launchEnv.DEEPCHAT_E2E_USER_DATA_DIR =
+ launchEnv.DEEPCHAT_E2E_USER_DATA_DIR ??
+ resolve(testInfo.outputDir, `deepchat-e2e-profile-${currentLaunch}`)
+ }
+
const electronApp = await electron.launch({
...(packaged
? {
executablePath: resolvePackagedExecutable(),
args: []
}
: {
args: ['.']
}),
cwd: REPO_ROOT,
- env: process.env,
+ env: launchEnv,
timeout: 120_000
})🤖 Prompt for AI Agents |
||
| timeout: 120_000 | ||
| }) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { defineConfig } from '@playwright/test' | ||
| import baseConfig from './playwright.config' | ||
|
|
||
| export default defineConfig({ | ||
| ...baseConfig, | ||
| testMatch: ['01-launch.smoke.spec.ts', '04-settings-navigation.smoke.spec.ts'], | ||
| retries: process.env.CI ? 1 : 0, | ||
| reporter: [['list'], ['html', { open: 'never' }]] | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.