Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ROLLBAR_POST_TOKEN=

API_URL=http://localhost:3000
UPLOAD_URL=http://localhost:3000
DATA_URL=http://localhost:3000
BLIP_URL=http://localhost:3000
DEBUG_ERROR=true
REDUX_LOG=false
REDUX_DEV_UI=false
E2E_USER_EMAIL=
E2E_USER_PASSWORD=
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ _book/
web/

\.vscode/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
videos
30 changes: 18 additions & 12 deletions app/components/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,38 @@
* == BSD2 LICENSE ==
*/

import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import styles from '../../styles/components/Login.module.less';
import { useDispatch, useSelector } from 'react-redux';

import actions from '../actions/';
const asyncActions = actions.async;

const remote = require('@electron/remote');
const i18n = remote.getGlobal( 'i18n' );

const i18n = remote.getGlobal('i18n');
import { keycloak } from '../keycloak';

export const Login = () => {
const dispatch = useDispatch();
const disabled = useSelector((state) => Boolean(state.unsupported));
const errorMessage = useSelector((state) => state.loginErrorMessage);
const forgotPasswordUrl = useSelector(
(state) => state.blipUrls.forgotPassword
);
const isLoggingIn = useSelector(
(state) => state.working.loggingIn.inProgress
);
const forgotPasswordUrl = useSelector((state) => state.blipUrls.forgotPassword);
const isLoggingIn = useSelector((state) => state.working.loggingIn.inProgress);
const keycloakConfig = useSelector((state) => state.keycloakConfig);
const keycloakInitializing = keycloakConfig.url && !keycloakConfig.initialized;
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [remember, setRemember] = useState(true);
const [loginUrl, setLoginUrl] = useState('');

useEffect(() => {
if (keycloakConfig.initialized) {
try {
setLoginUrl(keycloak.createLoginUrl());
} catch (error) {
console.error('Error creating Keycloak login URL:', error);
}
}
}, [keycloakConfig]);

const renderForgotPasswordLink = () => {
return (
Expand All @@ -52,7 +57,7 @@ export const Login = () => {
};

const renderButton = () => {
var text = i18n.t('Log in');
let text = i18n.t('Log in');

if (isLoggingIn) {
text = i18n.t('Logging in...');
Expand All @@ -68,6 +73,7 @@ export const Login = () => {
className={styles.button}
onClick={handleLogin}
disabled={isLoggingIn || disabled || keycloakInitializing}
data-testUrl={loginUrl ? loginUrl.toString() : ''}
>
{text}
</button>
Expand All @@ -77,7 +83,7 @@ export const Login = () => {
const handleLogin = (e) => {
e.preventDefault();
if (keycloakConfig.initialized) {
window.open(keycloak.createLoginUrl(), '_blank');
window.open(loginUrl, '_blank');
} else {
dispatch(asyncActions.doLogin({ username, password }, { remember }));
}
Expand Down
22 changes: 14 additions & 8 deletions app/main.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ function createWindow() {
height: 769,
resizable: resizable,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false, // so that we can access process from app.html
},
Expand Down Expand Up @@ -261,7 +262,7 @@ operating system, as soon as possible.`,

let selectedPort;
for (let i = 0; i < serialPortFilter.length; i++) {
selectedPort = portList.find((element) =>
selectedPort = portList.find((element) =>
serialPortFilter[i].usbVendorId === parseInt(element.vendorId, 10) &&
serialPortFilter[i].usbProductId === parseInt(element.productId, 10)
);
Expand Down Expand Up @@ -652,17 +653,22 @@ const handleIncomingUrl = (url) => {
const { webContents } = mainWindow;
// redirecting from the app html to app html with hash breaks devtools
// just send and append the hash if we're already in the app html
if (webContents.getURL().includes(baseURL)) {
webContents.send('newHash', requestHash);
} else {
// if (webContents.getURL().includes(baseURL)) {
// webContents.send('newHash', requestHash);
// } else {
webContents.loadURL(`${baseURL}${requestHash}`);
}
return;
// }
return;
}

}
};

ipcMain.handle('handle-incoming-url', async (event, url) => {
console.log('handle-incoming-url called with URL:', url);
handleIncomingUrl(url);
});

const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
Expand All @@ -678,7 +684,7 @@ if (!gotTheLock) {
return handleIncomingUrl(url);
}
});

// Protocol handler for osx
app.on('open-url', (event, url) => {
event.preventDefault();
Expand Down
12 changes: 12 additions & 0 deletions app/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { ipcRenderer } = require('electron');

console.log('Preload script is running');

window.electron = {
handleIncomingUrl: (url) => {
console.log('handleIncomingUrl called with URL:', url);
return ipcRenderer.invoke('handle-incoming-url', url);
}
};

console.log('Exposed handleIncomingUrl method to window.electron');
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"serve-docs": "./node_modules/.bin/gitbook serve",
"test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 jest",
"test-all": "npm run lint && npm run test && npm run build",
"test-e2e": "playwright test electron.test.js",
"lint": "node ./node_modules/eslint/bin/eslint.js --cache --format=node_modules/eslint-formatter-pretty .",
"lint-fix": "npm run lint -- --fix",
"build-main": "yarn build-main-quiet --progress --profile --colors",
Expand Down Expand Up @@ -131,7 +132,9 @@
"@babel/register": "7.23.7",
"@babel/runtime-corejs2": "7.23.9",
"@jest-runner/electron": "3.0.1",
"@playwright/test": "^1.42.1",
"@tidepool/direct-io": "3.0.2",
"@types/node": "^20.11.30",
"aws-sdk": "2.1544.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "26.6.3",
Expand All @@ -147,6 +150,7 @@
"cross-env": "7.0.3",
"css-loader": "5.2.7",
"difflet": "1.0.1",
"dotenv": "^16.4.5",
"drivelist": "11.1.0",
"electron": "27.3.0",
"electron-builder": "24.9.1",
Expand Down
47 changes: 47 additions & 0 deletions playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test');

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './test/e2e',
/* Run tests in files in parallel */
// globalTimeout: 10000,
timeout: 60000,
expect: {
timeout: 20000
},

fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
// reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
video: 'on-first-retry'
},
projects: [
{
name: 'Mocked',
use: {
/* Mock the network */
mode: 'default'
}
}
]
});

Empty file removed test/e2e.js
Empty file.
96 changes: 96 additions & 0 deletions test/e2e/electron.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const { test, expect, chromium } = require('@playwright/test');
const { startElectron } = require('./utils/electron');
require('dotenv').config();
// @ts-check
test.describe('Home screen', () => {
/** @type {import('@playwright/test').Page} */
let window;
/** @type {import('@playwright/test').ElectronApplication} */
let electronApp;

/** @type {import('@playwright/test').ChromiumBrowser} */
let browser;

// HOOKS
test.beforeEach(async () => {
electronApp = await startElectron();
window = await electronApp.firstWindow();
});

test.afterEach(async () => {
await electronApp.close();
});

// TESTS
test('has correct links', async () => {
expect(await window.getByRole('link', { name: 'Get Support' }).getAttribute('href'))
.toBe('http://support.tidepool.org/');
expect(await window.getByRole('link', { name: 'Privacy and Terms of Use' })
.getAttribute('href')).toBe('http://tidepool.org/legal/');
});

test('hovered links have correct colors', async () => {
const links = ['Get Support', 'Privacy and Terms of Use'];

for (let linkText of links) {
const linkElement = window.locator('a').getByText(linkText);
const colorBefore = await linkElement.evaluate((e) => {
return window.getComputedStyle(e).getPropertyValue('color');
});
expect(colorBefore).toBe('rgb(151, 151, 151)');

await linkElement.hover();
const color = await linkElement.evaluate((e) => {
return window.getComputedStyle(e).getPropertyValue('color');
});
expect(color).toBe('rgb(98, 124, 255)');
}
});

test('has correct title', async () => {
expect(await window.title()).toBe('Tidepool Uploader');
});

test('can login with patient account', async () => {
let url;
await new Promise(async (resolve) => {
await window.waitForSelector('body');
await window.waitForLoadState('domcontentloaded');

const login = window.getByRole('button', { name: 'Log in' });
url = await login.getAttribute('data-testurl');
console.log('[Electron][Auth URL] ', url);
electronApp.close();
resolve();
}).then(async () => {
browser = await chromium.launch();
console.log('[Chromium] Started 🎉');
const page = await browser.newPage();
await page.goto(url);
await page.getByPlaceholder('Email').waitFor('visible', { timeout: 10000 });
await page.getByPlaceholder('Email').fill(process.env.E2E_USER_EMAIL);
await page.getByRole('button', { name: 'Next' }).click();
await page.getByPlaceholder('Password').waitFor('visible', { timeout: 10000 });
await page.getByPlaceholder('Password').fill('tidepool');
await page.getByRole('button', { name: 'Log In' }).click();

console.log('[Chromium] Clicked Log In button');
console.log('[Chromium] Waiting for the next page');
const href = await page.getByRole('link', { name: 'Launch Uploader' }).getAttribute('href');
console.log(href);
await browser.close();

return href;
}).then(async (href) => {

electronApp = await startElectron();
window = await electronApp.firstWindow();

await window.waitForLoadState('domcontentloaded');
await window.evaluate((url) => {
window.electron.handleIncomingUrl(url);
}, href);
await expect(window.getByRole('heading', { name: 'Choose devices' })).toBeVisible();
});
});
});
17 changes: 17 additions & 0 deletions test/e2e/utils/electron.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

const { _electron: electron, } = require('@playwright/test');

/**
* Launches Electron using Playwright.
* @returns {Promise<import('@playwright/test').ElectronApplication>}
The Electron application instance.
*/
async function startElectron () {
return await electron.launch({
args: ['./app/main.prod.js'],

// recordVideo: { dir: './videos' },
});
}

exports.startElectron = startElectron;
9 changes: 0 additions & 9 deletions test/example.js

This file was deleted.

Loading