diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml index 6c2842c30..e895baf06 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-electron-linter-and-test.yml @@ -154,6 +154,10 @@ jobs: with: node-version: 20 + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + # Clean cache, continue on error - name: 'Cleanup cache' shell: powershell @@ -192,11 +196,25 @@ jobs: with: fetch-depth: 0 + - uses: actions/cache@v4 # v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/yarn.lock') }} + - name: Installing node uses: actions/setup-node@v3 with: node-version: 20 + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + # Clean cache, continue on error - name: 'Cleanup cache' shell: powershell @@ -221,6 +239,20 @@ jobs: # run: | # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}" + - name: Install Prerequisites + shell: 'powershell' + # https://github.com/actions/runner-images/issues/9538 + # https://github.com/microsoft/playwright/pull/30009/files + # https://github.com/tauri-apps/wry/issues/1268 + # Evergreen Bootstrapper + # The Bootstrapper is a tiny installer that downloads + # the Evergreen Runtime matching device architecture + # and installs it locally. + # https://developer.microsoft.com/en-us/microsoft-edge/webview2/consumer/?form=MA13LH + run: | + Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe' + Start-Process -FilePath setup.exe -Verb RunAs -Wait + - name: Linter and test shell: powershell run: | @@ -240,6 +272,10 @@ jobs: with: node-version: 20 + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + # Clean cache, continue on error - name: 'Cleanup cache' shell: powershell @@ -272,6 +308,15 @@ jobs: with: node-version: 20 + - name: Install Tauri dependencies + run: | + sudo apt update + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver + + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + - name: 'Cleanup cache' continue-on-error: true run: | @@ -368,6 +413,15 @@ jobs: with: node-version: 20 + - name: Install Tauri dependencies + run: | + sudo apt update + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver + + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + - name: 'Cleanup cache' continue-on-error: true run: | diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml index 9356c3f28..18f6c40e9 100644 --- a/.github/workflows/template-tauri-build-linux-x64.yml +++ b/.github/workflows/template-tauri-build-linux-x64.yml @@ -96,7 +96,7 @@ jobs: run: | cargo install ctoml - - name: Install Tauri dependecies + - name: Install Tauri dependencies run: | sudo apt update sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bf4d12b5..78bf4f0ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + } } diff --git a/Makefile b/Makefile index 56b50a9d2..6e69c602d 100644 --- a/Makefile +++ b/Makefile @@ -33,23 +33,14 @@ dev: install-and-build yarn copy:lib yarn dev -# Deprecated soon -dev-tauri: install-and-build - yarn install:cortex - yarn download:bin - yarn copy:lib - yarn dev:tauri - # Linting lint: install-and-build yarn lint # Testing test: lint - # yarn build:test - # yarn test:coverage - # Need e2e setup for tauri backend yarn test + yarn test:e2e # Builds and publishes the app build-and-publish: install-and-build diff --git a/package.json b/package.json index 4d9f8382e..7e525f07b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "workspaces": { "packages": [ "core", - "web-app" + "web-app", + "tests-e2e-js" ] }, "scripts": { @@ -13,6 +14,11 @@ "build": "yarn build:web && yarn build:tauri", "test": "yarn workspace @janhq/web-app test", "test:coverage": "yarn workspace @janhq/web-app test", + "test:prepare": "yarn build:icon && yarn copy:lib && yarn copy:assets:tauri && yarn build --no-bundle ", + "test:e2e:linux": "yarn test:prepare && xvfb-run yarn workspace tests-e2-js test", + "test:e2e:win32": "yarn test:prepare && yarn workspace tests-e2-js test", + "test:e2e:darwin": "echo 'E2E tests are not supported on macOS yet due to WebDriver limitations'", + "test:e2e": "run-script-os", "dev:web": "yarn workspace @janhq/web-app dev", "dev:tauri": "CLEAN=true yarn build:icon && yarn copy:assets:tauri && tauri dev", "install:cortex:linux:darwin": "cd src-tauri/binaries && ./download.sh", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c55c8ea4e..7068ffba6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,8 +19,8 @@ tauri-build = { version = "2.0.2", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.4.0", features = [ "protocol-asset", "macos-private-api", - "test", +tauri = { version = "2.5.0", features = [ "protocol-asset", "macos-private-api", + "test" ] } tauri-plugin-log = "2.0.0-rc" tauri-plugin-shell = "2.2.0" diff --git a/src-tauri/src/core/setup.rs b/src-tauri/src/core/setup.rs index 8d8a3d557..42ee0faa5 100644 --- a/src-tauri/src/core/setup.rs +++ b/src-tauri/src/core/setup.rs @@ -277,7 +277,12 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> { ]); #[cfg(target_os = "windows")] { - let resource_dir = app_handle_for_spawn.path().resource_dir().unwrap(); + let mut resource_dir = app_handle_for_spawn.path().resource_dir().unwrap(); + // If debug + #[cfg(debug_assertions)] + { + resource_dir = resource_dir.join("binaries"); + } let normalized_path = resource_dir.to_string_lossy().replace(r"\\?\", ""); let normalized_pathbuf = PathBuf::from(normalized_path); cmd = cmd.current_dir(normalized_pathbuf); @@ -286,12 +291,12 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> { #[cfg(not(target_os = "windows"))] { cmd = cmd.env("LD_LIBRARY_PATH", { - let current_app_data_dir = app_handle_for_spawn - .path() - .resource_dir() - .unwrap() - .join("binaries"); - let dest = current_app_data_dir.to_str().unwrap(); + let mut resource_dir = app_handle_for_spawn.path().resource_dir().unwrap(); + #[cfg(not(debug_assertions))] + { + resource_dir = resource_dir.join("binaries"); + } + let dest = resource_dir.to_str().unwrap(); let ld_path_env = std::env::var("LD_LIBRARY_PATH").unwrap_or_default(); format!("{}{}{}", ld_path_env, ":", dest) }); diff --git a/tests-e2e-js/.gitignore b/tests-e2e-js/.gitignore new file mode 100644 index 000000000..1521c8b76 --- /dev/null +++ b/tests-e2e-js/.gitignore @@ -0,0 +1 @@ +dist diff --git a/tests-e2e-js/package.json b/tests-e2e-js/package.json new file mode 100644 index 000000000..e7dc18587 --- /dev/null +++ b/tests-e2e-js/package.json @@ -0,0 +1,23 @@ +{ + "name": "tests-e2-js", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "src/main.ts", + "scripts": { + "build": "tsc", + "test": "node --test --test-force-exit --loader ts-node/esm ./src/main.ts" + }, + "dependencies": { + "@tauri-e2e/selenium": "0.2.2", + "log4js": "^6.9.1", + "selenium-webdriver": "^4.22.0", + "ts-node": "^10.9.2" + }, + "devDependencies": { + "@types/node": "^20.14.9", + "@types/selenium-webdriver": "^4.1.28", + "tsimp": "^2.0.11", + "typescript": "^5.5.2" + } +} diff --git a/tests-e2e-js/src/main.ts b/tests-e2e-js/src/main.ts new file mode 100644 index 000000000..c6e0aeba6 --- /dev/null +++ b/tests-e2e-js/src/main.ts @@ -0,0 +1,51 @@ +import assert from 'node:assert' +import { ChildProcess } from 'node:child_process' +import { afterEach, beforeEach, describe, test } from 'node:test' +import { By, until, WebDriver } from 'selenium-webdriver' +import * as e2e from '@tauri-e2e/selenium' +import { default as log4js } from 'log4js' + +let logger = log4js.getLogger() +logger.level = 'debug' + +process.env.TAURI_WEBDRIVER_LOGLEVEL = 'debug' +process.env.TAURI_WEBDRIVER_BINARY = await e2e.install.PlatformDriver() +process.env.TAURI_SELENIUM_BINARY = '../src-tauri/target/release/Jan.exe' +process.env.SELENIUM_REMOTE_URL = 'http://127.0.0.1:6655' + +//@ts-ignore fuck you javascript +e2e.setLogger(logger) + +describe('Tauri E2E tests', async () => { + let driver: WebDriver + let webDriver: ChildProcess + + beforeEach(async () => { + // Spawn WebDriver process. + webDriver = await e2e.launch.spawnWebDriver() + // wait 1 second + await new Promise((r) => setTimeout(r, 1000)) + // Create driver session. + driver = new e2e.selenium.Builder().build() + // Wait for the body element to be present + // await driver.wait(until.elementLocated({ css: 'body' })) + }) + + afterEach(async () => { + await e2e.selenium.cleanupSession(driver) + e2e.launch.killWebDriver(webDriver) + }) + + test('Find hub', async () => { + const hub = until.elementLocated(By.css('[data-test-id="menu-common:hub"')) + // console.log('GG', hub) + // @ts-ignore + await driver.wait(hub.fn, 120000) + + const menuElement = await driver.findElement({ + css: '[data-test-id="menu-common:hub"]', + }) + assert(menuElement !== null, 'Hub menu element should be available') + await menuElement.isDisplayed() + }) +}) diff --git a/tests-e2e-js/tsconfig.json b/tests-e2e-js/tsconfig.json new file mode 100644 index 000000000..6189f7d38 --- /dev/null +++ b/tests-e2e-js/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "src/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ], +} diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index 2d16b2eee..0cecb2bf3 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -365,6 +365,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { rows={1} maxRows={10} value={prompt} + data-test-id={'chat-input'} onChange={(e) => { setPrompt(e.target.value) // Count the number of newlines to estimate rows @@ -567,6 +568,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { variant={!prompt.trim() ? null : 'default'} size="icon" disabled={!prompt.trim()} + data-test-id="send-message-button" onClick={() => handleSendMesage(prompt)} > {streamingContent ? ( diff --git a/web-app/src/containers/LeftPanel.tsx b/web-app/src/containers/LeftPanel.tsx index 5d0b1542e..ea4fbafc2 100644 --- a/web-app/src/containers/LeftPanel.tsx +++ b/web-app/src/containers/LeftPanel.tsx @@ -324,6 +324,7 @@ const LeftPanel = () => { )} {isDownloaded ? ( - handleUseModel(modelId)}> + handleUseModel(modelId)} + data-test-id={`hub-model-${modelId}`} + > {t('hub:use')} ) : ( @@ -244,7 +244,10 @@ function ThreadDetail() { ) })} - + =1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + +"tsimp@npm:^2.0.11": + version: 2.0.12 + resolution: "tsimp@npm:2.0.12" + dependencies: + "@isaacs/cached": "npm:^1.0.1" + "@isaacs/catcher": "npm:^1.0.4" + foreground-child: "npm:^3.1.1" + mkdirp: "npm:^3.0.1" + pirates: "npm:^4.0.6" + rimraf: "npm:^6.0.1" + signal-exit: "npm:^4.1.0" + sock-daemon: "npm:^1.4.2" + walk-up-path: "npm:^4.0.0" + peerDependencies: + typescript: ^5.1.0 + bin: + tsimp: dist/esm/bin.mjs + checksum: 10c0/c56c03a6a4df3ab5ebcefcc0b473992cbb7150173c331be6bda01670d5ae3965e65f30c42757cd391100a1c21485e167a05a350d875f41826b35c45008e5fac8 + languageName: node + linkType: hard + "tslib@npm:^1.11.1, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -18434,7 +18915,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.8.3, typescript@npm:~5.8.3": +"typescript@npm:^5.5.2, typescript@npm:^5.8.3, typescript@npm:~5.8.3": version: 5.8.3 resolution: "typescript@npm:5.8.3" bin: @@ -18444,7 +18925,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin, typescript@patch:typescript@npm%3A~5.8.3#optional!builtin": +"typescript@patch:typescript@npm%3A^5.5.2#optional!builtin, typescript@patch:typescript@npm%3A^5.8.3#optional!builtin, typescript@patch:typescript@npm%3A~5.8.3#optional!builtin": version: 5.8.3 resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" bin: @@ -18919,6 +19400,13 @@ __metadata: languageName: node linkType: hard +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.1": version: 9.3.0 resolution: "v8-to-istanbul@npm:9.3.0" @@ -19233,6 +19721,13 @@ __metadata: languageName: node linkType: hard +"walk-up-path@npm:^4.0.0": + version: 4.0.0 + resolution: "walk-up-path@npm:4.0.0" + checksum: 10c0/fabe344f91387d1d41df230af962ef18bf703dd4178006d55cd6412caacd187b54440002d4d53a982d4f7f0455567dcffb6d3884533c8b2268928eca3ebd8a19 + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -19472,6 +19967,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.18.2": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + "xml-name-validator@npm:^4.0.0": version: 4.0.0 resolution: "xml-name-validator@npm:4.0.0" @@ -19546,6 +20056,13 @@ __metadata: languageName: node linkType: hard +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" @@ -19553,6 +20070,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.2.1 + resolution: "yocto-queue@npm:1.2.1" + checksum: 10c0/5762caa3d0b421f4bdb7a1926b2ae2189fc6e4a14469258f183600028eb16db3e9e0306f46e8ebf5a52ff4b81a881f22637afefbef5399d6ad440824e9b27f9f + languageName: node + linkType: hard + "zod@npm:^3.23.8": version: 3.24.2 resolution: "zod@npm:3.24.2"