diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 488d427d85..ef880fbda9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,16 +17,6 @@ jobs: uses: ./.github/actions/setup-node - run: yarn lint --max-warnings=0 - install-all: - name: Install All Dependencies - runs-on: ubuntu-latest - environment: development - - steps: - - uses: actions/checkout@v3 - - name: Install All Dependencies - uses: ./.github/actions/setup-node - build-storybook: name: Build Storybook runs-on: ubuntu-latest @@ -44,6 +34,23 @@ jobs: path: ./packages/component/storybook-static if-no-files-found: error + build-electron: + name: Build @affine/electron + runs-on: ubuntu-latest + environment: development + steps: + - uses: actions/checkout@v3 + - name: Setup Node.js + uses: ./.github/actions/setup-node + - name: Build Electron + working-directory: apps/electron + run: yarn exec ts-node-esm ./scripts/build-ci.mts + - name: Upload Ubuntu desktop artifact + uses: actions/upload-artifact@v3 + with: + name: affine-ubuntu + path: ./apps/electron/dist + build: name: Build @affine/web runs-on: ubuntu-latest @@ -230,6 +237,48 @@ jobs: path: ./test-results if-no-files-found: ignore + dekstop-test: + name: Desktop Test + runs-on: ubuntu-latest + environment: development + needs: [build, build-electron] + steps: + - uses: actions/checkout@v3 + - name: Setup Node.js + uses: ./.github/actions/setup-node + with: + playwright-install: true + - name: Download Ubuntu desktop artifact + uses: actions/download-artifact@v3 + with: + name: affine-ubuntu + path: ./apps/electron/dist + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: next-js + path: ./apps/web/.next + + - name: Generate static files + run: yarn export + working-directory: ./apps/web + + - name: Move static files to electron + run: mv ./apps/web/out ./apps/electron/resources/web-static + + - name: Run desktop tests + run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test + working-directory: apps/electron + + - name: Upload test results + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: test-results-e2e-${{ matrix.shard }} + path: ./test-results + if-no-files-found: ignore + unit-test: name: Unit Test runs-on: ubuntu-latest diff --git a/README.md b/README.md index d8b4104030..6c97c680f1 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,6 @@ We would like to express our gratitude to all the individuals who have already c - ## Self-Host Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE - check the [latest packages]. diff --git a/apps/electron/layers/main/src/main-window.ts b/apps/electron/layers/main/src/main-window.ts index b725b96209..99ecbd9dd7 100644 --- a/apps/electron/layers/main/src/main-window.ts +++ b/apps/electron/layers/main/src/main-window.ts @@ -5,7 +5,8 @@ import { join } from 'path'; import { logger } from '../../logger'; import { isMacOS } from '../../utils'; -const IS_DEV = process.env.NODE_ENV === 'development'; +const IS_DEV: boolean = + process.env.NODE_ENV === 'development' && !process.env.CI; async function createWindow() { logger.info('create window'); diff --git a/apps/electron/package.json b/apps/electron/package.json index 70664c4b81..0aebf29430 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -16,7 +16,8 @@ "make-windows-x64": "electron-forge make --platform=win32 --arch=x64", "make-linux-x64": "electron-forge make --platform=linux --arch=x64", "rebuild:for-test": "yarn rebuild better-sqlite3", - "rebuild:for-electron": "yarn electron-rebuild" + "rebuild:for-electron": "yarn electron-rebuild", + "test": "playwright test" }, "config": { "forge": "./forge.config.js" @@ -42,6 +43,8 @@ "electron-window-state": "^5.0.3", "esbuild": "^0.17.18", "fs-extra": "^11.1.1", + "playwright": "^1.32.3", + "ts-node": "^10.9.1", "undici": "^5.22.0", "zx": "^7.2.1" }, @@ -62,5 +65,9 @@ "stableVersion": "0.5.3", "installConfig": { "hoistingLimits": "workspaces" + }, + "peerDependencies": { + "playwright": "*", + "ts-node": "*" } } diff --git a/apps/electron/playwright.config.ts b/apps/electron/playwright.config.ts new file mode 100644 index 0000000000..4e91b35964 --- /dev/null +++ b/apps/electron/playwright.config.ts @@ -0,0 +1,27 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +// import { devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + fullyParallel: true, + timeout: process.env.CI ? 50_000 : 30_000, + use: { + viewport: { width: 1440, height: 800 }, + }, +}; + +if (process.env.CI) { + config.retries = 3; + config.workers = '50%'; +} + +export default config; diff --git a/apps/electron/scripts/build-ci.mts b/apps/electron/scripts/build-ci.mts new file mode 100755 index 0000000000..ef6c70339f --- /dev/null +++ b/apps/electron/scripts/build-ci.mts @@ -0,0 +1,17 @@ +#!/usr/bin/env ts-node-esm +import * as esbuild from 'esbuild'; + +import { config } from './common.mjs'; + +const common = config(); +await esbuild.build(common.preload); + +await esbuild.build({ + ...common.main, + define: { + ...common.main.define, + 'process.env.NODE_ENV': `"production"`, + }, +}); + +console.log('Compiled successfully.'); diff --git a/apps/electron/scripts/common.mjs b/apps/electron/scripts/common.mjs index fbae63ae5e..10240d3a5f 100644 --- a/apps/electron/scripts/common.mjs +++ b/apps/electron/scripts/common.mjs @@ -1,4 +1,9 @@ -const NODE_MAJOR_VERSION = 18; +import { resolve } from 'node:path'; + +import { fileURLToPath } from 'url'; + +export const root = fileURLToPath(new URL('..', import.meta.url)); +export const NODE_MAJOR_VERSION = 18; const nativeNodeModulesPlugin = { name: 'native-node-modules', @@ -14,7 +19,7 @@ const nativeNodeModulesPlugin = { const ENV_MACROS = ['AFFINE_GOOGLE_CLIENT_ID', 'AFFINE_GOOGLE_CLIENT_SECRET']; /** @return {{main: import('esbuild').BuildOptions, preload: import('esbuild').BuildOptions}} */ -export default () => { +export const config = () => { const define = Object.fromEntries( ENV_MACROS.map(key => [ 'process.env.' + key, @@ -23,8 +28,8 @@ export default () => { ); return { main: { - entryPoints: ['layers/main/src/index.ts'], - outdir: 'dist/layers/main', + entryPoints: [resolve(root, './layers/main/src/index.ts')], + outdir: resolve(root, './dist/layers/main'), bundle: true, target: `node${NODE_MAJOR_VERSION}`, platform: 'node', @@ -33,8 +38,8 @@ export default () => { define: define, }, preload: { - entryPoints: ['layers/preload/src/index.ts'], - outdir: 'dist/layers/preload', + entryPoints: [resolve(root, './layers/preload/src/index.ts')], + outdir: resolve(root, './dist/layers/preload'), bundle: true, target: `node${NODE_MAJOR_VERSION}`, platform: 'node', diff --git a/apps/electron/scripts/dev.mjs b/apps/electron/scripts/dev.mjs index ffca8e4274..cc4399b239 100644 --- a/apps/electron/scripts/dev.mjs +++ b/apps/electron/scripts/dev.mjs @@ -1,15 +1,11 @@ import { spawn } from 'node:child_process'; import { readFileSync } from 'node:fs'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import electronPath from 'electron'; import * as esbuild from 'esbuild'; -import commonFn from './common.mjs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +import { config, root } from './common.mjs'; /** @type 'production' | 'development'' */ const mode = (process.env.NODE_ENV = process.env.NODE_ENV || 'development'); @@ -22,9 +18,9 @@ const stderrFilterPatterns = [ /ExtensionLoadWarning/, ]; -// these are set before calling commonFn so we have a chance to override them +// these are set before calling `config`, so we have a chance to override them try { - const devJson = readFileSync(path.resolve(__dirname, '../dev.json'), 'utf-8'); + const devJson = readFileSync(path.resolve(root, './dev.json'), 'utf-8'); const devEnv = JSON.parse(devJson); Object.assign(process.env, devEnv); } catch (err) { @@ -67,7 +63,7 @@ function spawnOrReloadElectron() { spawnProcess.on('exit', process.exit); } -const common = commonFn(); +const common = config(); async function main() { async function watchPreload(onInitialBuild) { diff --git a/apps/electron/scripts/generate-assets.mjs b/apps/electron/scripts/generate-assets.mjs index 38f6421381..f5f416fa5b 100644 --- a/apps/electron/scripts/generate-assets.mjs +++ b/apps/electron/scripts/generate-assets.mjs @@ -5,7 +5,7 @@ import path from 'node:path'; import * as esbuild from 'esbuild'; -import commonFn from './common.mjs'; +import { config } from './common.mjs'; const repoRootDir = path.join(__dirname, '..', '..', '..'); const electronRootDir = path.join(__dirname, '..'); @@ -77,7 +77,7 @@ async function cleanup() { } async function buildLayers() { - const common = commonFn(); + const common = config(); await esbuild.build(common.preload); await esbuild.build({ diff --git a/apps/electron/tests/basic.spec.ts b/apps/electron/tests/basic.spec.ts new file mode 100644 index 0000000000..1062d2dba0 --- /dev/null +++ b/apps/electron/tests/basic.spec.ts @@ -0,0 +1,22 @@ +import { resolve } from 'node:path'; + +import { expect, test } from '@playwright/test'; +import { _electron as electron } from 'playwright'; + +test('new page', async () => { + const electronApp = await electron.launch({ + args: [resolve(__dirname, '..')], + executablePath: resolve(__dirname, '../node_modules/.bin/electron'), + }); + const page = await electronApp.firstWindow(); + await page.getByTestId('new-page-button').click({ + delay: 100, + }); + await page.waitForSelector('v-line'); + const flavour = await page.evaluate( + // @ts-expect-error + () => globalThis.currentWorkspace.flavour + ); + expect(flavour).toBe('local'); + await electronApp.close(); +}); diff --git a/apps/electron/tests/tsconfig.json b/apps/electron/tests/tsconfig.json new file mode 100644 index 0000000000..4c43f890a3 --- /dev/null +++ b/apps/electron/tests/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "noEmit": true + }, + "include": ["**.spec.ts", "**.test.ts"] +} diff --git a/apps/electron/tsconfig.json b/apps/electron/tsconfig.json new file mode 100644 index 0000000000..59f8b6d127 --- /dev/null +++ b/apps/electron/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "target": "ESNext", + "module": "ESNext", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "isolatedModules": false, + "resolveJsonModule": true, + "types": ["node"], + "outDir": "dist", + "noEmit": false + }, + "include": ["layers", "types", "package.json"], + "exclude": ["out", "dist", "node_modules"], + "references": [ + { + "path": "./tsconfig.node.json" + } + ], + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node" + } +} diff --git a/apps/electron/tsconfig.node.json b/apps/electron/tsconfig.node.json new file mode 100644 index 0000000000..b9e486d423 --- /dev/null +++ b/apps/electron/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ESNext", + "module": "ESNext", + "resolveJsonModule": true, + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["./scripts", "package.json"] +} diff --git a/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx index d74b9117d6..87c1cf1098 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx @@ -37,7 +37,7 @@ export const ChangeLog = () => { diff --git a/apps/web/src/components/pure/workspace-slider-bar/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/index.tsx index d352c34e63..dc5f18e37d 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/index.tsx @@ -1,4 +1,3 @@ - import { config } from '@affine/env'; import { useTranslation } from '@affine/i18n'; import { WorkspaceFlavour } from '@affine/workspace/type'; diff --git a/tsconfig.json b/tsconfig.json index 2d17ebe792..b77bee95d0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,6 +37,9 @@ { "path": "./tests" }, + { + "path": "./apps/electron/tests" + }, { "path": "./apps/web" }, @@ -66,6 +69,9 @@ }, { "path": "./tsconfig.node.json" + }, + { + "path": "./apps/electron" } ], "files": [], diff --git a/vitest.config.ts b/vitest.config.ts index 0769711588..5c127c3c4a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -27,10 +27,10 @@ export default defineConfig({ 'packages/**/*.spec.tsx', 'apps/web/**/*.spec.ts', 'apps/web/**/*.spec.tsx', - 'apps/electron/**/*.spec.ts', 'tests/unit/**/*.spec.ts', 'tests/unit/**/*.spec.tsx', ], + exclude: ['**/node_modules', '**/dist', '**/build', '**/out'], testTimeout: 5000, coverage: { provider: 'istanbul', // or 'c8' diff --git a/yarn.lock b/yarn.lock index b1ea409bc6..01e0c822a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -132,9 +132,14 @@ __metadata: electron-window-state: ^5.0.3 esbuild: ^0.17.18 fs-extra: ^11.1.1 + playwright: ^1.32.3 + ts-node: ^10.9.1 undici: ^5.22.0 yjs: ^13.6.0 zx: ^7.2.1 + peerDependencies: + playwright: "*" + ts-node: "*" languageName: unknown linkType: soft @@ -18914,7 +18919,7 @@ __metadata: languageName: node linkType: hard -"playwright@npm:^1.14.0": +"playwright@npm:^1.14.0, playwright@npm:^1.32.3": version: 1.32.3 resolution: "playwright@npm:1.32.3" dependencies: