diff --git a/.eslintignore b/.eslintignore index a6f34fe..9d4905b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ node_modules dist out .gitignore +.env diff --git a/.gitignore b/.gitignore index 42bd71b..8a00eaf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist out .DS_Store *.log* +.env diff --git a/.prettierignore b/.prettierignore index 9c6b791..daf84ea 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,4 @@ pnpm-lock.yaml LICENSE.md tsconfig.json tsconfig.*.json +.env diff --git a/package-lock.json b/package-lock.json index 206ea8c..242f6df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,12 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/utils": "^3.0.0", + "@playwright/test": "^1.45.1", "dexie": "^4.0.7", "dexie-react-hooks": "^1.1.7", + "dotenv": "^16.4.5", "electron-updater": "^6.1.7", + "playwright": "^1.45.1", "react-router-dom": "^6.23.1", "react-select": "^5.8.0" }, @@ -1659,6 +1662,20 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz", + "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==", + "dependencies": { + "playwright": "1.45.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -3696,12 +3713,14 @@ } }, "node_modules/dotenv": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", - "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", - "dev": true, + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -6994,6 +7013,47 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz", + "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==", + "dependencies": { + "playwright-core": "1.45.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz", + "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -7434,6 +7494,15 @@ "node": ">=12.0.0" } }, + "node_modules/read-config-file/node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", diff --git a/package.json b/package.json index f065c7b..0e8799d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "format": "prettier --write .", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", + "test:playwright": "playwright test", "start": "electron-vite preview", "dev": "electron-vite dev --watch", "build": "electron-vite build", @@ -20,9 +21,12 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/utils": "^3.0.0", + "@playwright/test": "^1.45.1", "dexie": "^4.0.7", "dexie-react-hooks": "^1.1.7", + "dotenv": "^16.4.5", "electron-updater": "^6.1.7", + "playwright": "^1.45.1", "react-router-dom": "^6.23.1", "react-select": "^5.8.0" }, diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..3398d06 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,18 @@ +// playwright.config.js +module.exports = { + use: { + headless: false + }, + projects: [ + { + name: 'electron', + use: { + browserName: 'chromium', + channel: 'chrome', + launchOptions: { + args: ['--no-sandbox'] + } + } + } + ] +} diff --git a/src/main/index.js b/src/main/index.js index e9492a9..8916ebd 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,7 +1,7 @@ -import { app, shell, BrowserWindow, ipcMain, protocol, net } from 'electron' -import { join, path } from 'path' -import { electronApp, optimizer, is } from '@electron-toolkit/utils' -import icon from '../../resources/icon.png?asset' +const { app, shell, BrowserWindow, ipcMain, protocol, net } = require('electron') +const { path, join } = require('path') +const { electronApp, optimizer, is } = require('@electron-toolkit/utils') +const iconPath = join(__dirname, '../../resources/icon.png') function createWindow() { // Create the browser window. @@ -10,7 +10,8 @@ function createWindow() { height: 670, show: false, autoHideMenuBar: true, - ...(process.platform === 'linux' ? { icon } : {}), + icon: iconPath, + // ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..cbcc1fb --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/tests/example.spec.js b/tests/example.spec.js new file mode 100644 index 0000000..7fd5443 --- /dev/null +++ b/tests/example.spec.js @@ -0,0 +1,70 @@ +import { _electron as electron } from 'playwright' +import { test, expect } from '@playwright/test' +import path from 'path' +import { execSync } from 'child_process' +import dotenv from 'dotenv' + +dotenv.config() + +test.describe('Electron app', () => { + let electronApp + + test.beforeAll(async () => { + // Build the application before running the tests + execSync('npm run build', { stdio: 'inherit' }) + + // Path to the built Electron app + const appPath = path.join( + __dirname, + '..', + 'dist', + 'mac-arm64', + 'DHIS2 Downloader.app', + 'Contents', + 'MacOS', + 'DHIS2 Downloader' + ) + electronApp = await electron.launch({ + executablePath: appPath + }) + }) + + // Remove the test.afterAll hook to prevent the app from closing + test.afterAll(async () => { + await electronApp.close() + }) + + test.beforeEach(async () => { + const window = await electronApp.firstWindow() + // Clear all local storage before each test + await window.evaluate(() => localStorage.clear()) + }) + + test('should have a navbar with clickable elements', async () => { + const window = await electronApp.firstWindow() + const navLinks = await window.$$('xpath=//navbar//a') + + // Check that each link is clickable + for (const link of navLinks) { + const isClickable = await link.isClickable() + expect(isClickable).toBe(true) + } + }) + + test('Could fill in Login Form', async () => { + const window = await electronApp.firstWindow() + const dhis2UrlInput = await window.locator('xpath=//form//input[@placeholder="DHIS2 URL"]') + await dhis2UrlInput.fill(process.env.TEST_DHIS2_URL) + + // Fill in other input fields (example) + const usernameInput = await window.locator('xpath=//form//input[@placeholder="Username"]') + await usernameInput.fill(process.env.TEST_USERNAME) + + const passwordInput = await window.locator('xpath=//form//input[@placeholder="Password"]') + await passwordInput.fill(process.env.TEST_PASSWORD) + + // Optionally, click the submit button + const submitButton = await window.locator('xpath=//form//button[@type="submit"]') + await submitButton.click() + }) +})