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 ? ( - ) : (