Merge pull request #2582 from oxen-io/clearnet

Session 1.10.4
pull/2661/head v1.10.4
Audric Ackermann 2 years ago committed by GitHub
commit 991ddd1cba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.10.3",
"version": "1.10.4",
"license": "GPL-3.0",
"author": {
"name": "Oxen Labs",
@ -306,7 +306,8 @@
"libnss3",
"libasound2",
"libxss1"
]
],
"packageCategory": "net"
},
"files": [
"package.json",

@ -42,12 +42,18 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
}
public componentDidMount() {
document.addEventListener('keyup', this.onEnterPressed);
setTimeout(() => {
// tslint:disable-next-line: no-unused-expression
this.passportInput && this.passportInput.focus();
}, 1);
}
public componentWillUnmount() {
document.removeEventListener('keyup', this.onEnterPressed);
}
public render() {
const { passwordAction } = this.props;
let placeholders: Array<string> = [];
@ -93,7 +99,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.passportInput = input;
}}
placeholder={placeholders[0]}
onKeyUp={this.onPasswordInput}
onChange={this.onPasswordInput}
onPaste={this.onPasswordInput}
data-testid="password-input"
/>
{passwordAction !== 'enter' && passwordAction !== 'remove' && (
@ -101,7 +108,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
type="password"
id="password-modal-input-confirm"
placeholder={placeholders[1]}
onKeyUp={this.onPasswordConfirmInput}
onChange={this.onPasswordConfirmInput}
onPaste={this.onPasswordConfirmInput}
data-testid="password-input-confirm"
/>
)}
@ -110,7 +118,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
type="password"
id="password-modal-input-reconfirm"
placeholder={placeholders[2]}
onKeyUp={this.onPasswordRetypeInput}
onPaste={this.onPasswordRetypeInput}
onChange={this.onPasswordRetypeInput}
data-testid="password-input-reconfirm"
/>
)}
@ -258,6 +267,13 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.closeDialog();
}
private async onEnterPressed(event: any) {
if (event.key === 'Enter') {
event.stopPropagation();
return this.setPassword();
}
}
private async handleActionEnter(enteredPassword: string) {
// be sure the password is valid
if (!this.validatePassword(enteredPassword)) {
@ -321,30 +337,18 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
window.inboxStore?.dispatch(sessionPassword(null));
}
private async onPasswordInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword();
}
private onPasswordInput(event: any) {
const currentPasswordEntered = event.target.value;
this.setState({ currentPasswordEntered });
}
private async onPasswordConfirmInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword();
}
private onPasswordConfirmInput(event: any) {
const currentPasswordConfirmEntered = event.target.value;
this.setState({ currentPasswordConfirmEntered });
}
private async onPasswordRetypeInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword();
}
private onPasswordRetypeInput(event: any) {
const currentPasswordRetypeEntered = event.target.value;
this.setState({ currentPasswordRetypeEntered });
}
}

@ -7,12 +7,10 @@ type Props = {
const StyledCountContainer = styled.div<{ shouldRender: boolean }>`
position: absolute;
width: 24px;
height: 12px;
font-size: 18px;
top: 27px;
right: 8px;
padding: 3px;
padding: 0 6px;
opacity: 1;
display: flex;
align-items: center;
@ -29,19 +27,19 @@ const StyledCountContainer = styled.div<{ shouldRender: boolean }>`
const StyledCount = styled.div`
position: relative;
font-size: 0.6em;
font-size: 0.6rem;
`;
export const SessionNotificationCount = (props: Props) => {
const { count } = props;
const overflow = Boolean(count && count > 9);
const overflow = Boolean(count && count > 99);
const shouldRender = Boolean(count && count > 0);
if (overflow) {
return (
<StyledCountContainer shouldRender={shouldRender}>
<StyledCount>
{9}
{99}
<span>+</span>
</StyledCount>
</StyledCountContainer>

@ -117,7 +117,6 @@ const Section = (props: { type: SectionType }) => {
iconSize="medium"
dataTestId="settings-section"
iconType={'gear'}
notificationCount={unreadToShow}
onClick={handleClick}
isSelected={isSelected}
/>
@ -138,7 +137,6 @@ const Section = (props: { type: SectionType }) => {
iconSize="medium"
iconType={isDarkMode ? 'moon' : 'sun'}
dataTestId="theme-section"
notificationCount={unreadToShow}
onClick={handleClick}
isSelected={isSelected}
/>

@ -97,6 +97,7 @@ export const SettingsCategoryPrivacy = (props: {
displayPasswordModal('change', props.onPasswordUpdated);
}}
buttonText={window.i18n('changePassword')}
dataTestId="change-password-settings-button"
/>
)}
{props.hasPassword && (
@ -108,6 +109,7 @@ export const SettingsCategoryPrivacy = (props: {
}}
buttonColor={SessionButtonColor.Danger}
buttonText={window.i18n('removePassword')}
dataTestId="remove-password-settings-button"
/>
)}
</>

@ -142,36 +142,33 @@ const defaultServerPublicKey = 'a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f4
const defaultRoom = `${defaultServer}/main?public_key=${defaultServerPublicKey}`;
const loadDefaultRoomsSingle = () =>
allowOnlyOneAtATime(
'loadDefaultRoomsSingle',
async (): Promise<Array<OpenGroupV2InfoJoinable>> => {
const roomInfos = parseOpenGroupV2(defaultRoom);
if (roomInfos) {
try {
const roomsGot = await getAllRoomInfos(roomInfos);
if (!roomsGot) {
return [];
}
return roomsGot.map(room => {
return {
...room,
completeUrl: getCompleteUrlFromRoom({
serverUrl: roomInfos.serverUrl,
serverPublicKey: roomInfos.serverPublicKey,
roomId: room.id,
}),
};
});
} catch (e) {
window?.log?.warn('loadDefaultRoomloadDefaultRoomssIfNeeded failed', e);
allowOnlyOneAtATime('loadDefaultRoomsSingle', async () => {
const roomInfos = parseOpenGroupV2(defaultRoom);
if (roomInfos) {
try {
const roomsGot = await getAllRoomInfos(roomInfos);
if (!roomsGot) {
return [];
}
return [];
return roomsGot.map(room => {
return {
...room,
completeUrl: getCompleteUrlFromRoom({
serverUrl: roomInfos.serverUrl,
serverPublicKey: roomInfos.serverPublicKey,
roomId: room.id,
}),
};
});
} catch (e) {
window?.log?.warn('loadDefaultRoomloadDefaultRoomssIfNeeded failed', e);
}
return [];
}
);
return [];
});
/**
* Load to the cache all the details of the room of the default opengroupv2 server

@ -45,7 +45,7 @@ export class OpenGroupManagerV2 {
serverUrl: string,
roomId: string,
publicKey: string
): Promise<ConversationModel> {
): Promise<ConversationModel | undefined> {
const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${serverUrl}${roomId}`;
return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
return this.attemptConnectionV2(serverUrl, roomId, publicKey);

@ -5,7 +5,7 @@ import { OpenGroupData, OpenGroupV2Room } from '../../../../data/opengroups';
import AbortController, { AbortSignal } from 'abort-controller';
import { batchGlobalIsSuccess } from './sogsV3BatchPoll';
export const capabilitiesFetchForServer = async (
const capabilitiesFetchForServer = async (
serverUrl: string,
serverPubKey: string,
abortSignal: AbortSignal
@ -13,7 +13,8 @@ export const capabilitiesFetchForServer = async (
const endpoint = '/capabilities';
const method = 'GET';
const serverPubkey = serverPubKey;
const blinded = false; // for capabilities, blinding is always false as the request will fail if the server requires blinding
// for the capabilities call, we require blinded to be ON now. A sogs with blinding disabled will still allow this call and verify the blinded signature
const blinded = true;
const capabilityHeaders = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
serverPubkey,
endpoint,
@ -33,7 +34,6 @@ export const capabilitiesFetchForServer = async (
serverPubkey,
serverUrl,
stringifiedBody: null,
doNotIncludeOurSogsHeaders: true, // the first capabilities needs to not have any authentification to pass on a blinding-required sogs,
headers: null,
throwErrors: false,
});

@ -1,6 +1,10 @@
import AbortController, { AbortSignal } from 'abort-controller';
import { isUndefined, toNumber } from 'lodash';
import { OpenGroupV2Room, OpenGroupV2RoomWithImageID } from '../../../../data/opengroups';
import {
OpenGroupData,
OpenGroupV2Room,
OpenGroupV2RoomWithImageID,
} from '../../../../data/opengroups';
import { MIME } from '../../../../types';
import { processNewAttachment } from '../../../../types/MessageAttachment';
import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
@ -16,7 +20,6 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
serverPubkey: string;
blinded: boolean;
abortSignal: AbortSignal;
doNotIncludeOurSogsHeaders?: boolean;
headers: Record<string, any> | null;
roomId: string;
fileId: string;
@ -28,7 +31,6 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
blinded,
abortSignal,
headers: includedHeaders,
doNotIncludeOurSogsHeaders,
roomId,
fileId,
throwError,
@ -41,15 +43,13 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
throw new Error('endpoint needs a leading /');
}
const builtUrl = new URL(`${serverUrl}${endpoint}`);
let headersWithSogsHeadersIfNeeded = doNotIncludeOurSogsHeaders
? {}
: await OpenGroupPollingUtils.getOurOpenGroupHeaders(
serverPubkey,
endpoint,
method,
blinded,
stringifiedBody
);
let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
serverPubkey,
endpoint,
method,
blinded,
stringifiedBody
);
if (isUndefined(headersWithSogsHeadersIfNeeded)) {
return null;
@ -98,12 +98,15 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith
return;
}
const room = OpenGroupData.getV2OpenGroupRoom(convoId);
const blinded = roomHasBlindEnabled(room);
// make sure this runs only once for each rooms.
// we don't want to trigger one of those on each setPollInfo resultsas it happens on each batch poll.
const oneAtAtimeResult = (await allowOnlyOneAtATime(
// we don't want to trigger one of those on each setPollInfo results as it happens on each batch poll.
const oneAtAtimeResult = await allowOnlyOneAtATime(
`sogsV3FetchPreview-${serverUrl}-${roomId}`,
() => sogsV3FetchPreview(roomInfos)
)) as Uint8Array | null; // force the return type as allowOnlyOneAtATime does not keep it
() => sogsV3FetchPreview(roomInfos, blinded)
);
if (!oneAtAtimeResult || !oneAtAtimeResult?.byteLength) {
window?.log?.warn('sogsV3FetchPreviewAndSaveIt failed for room: ', roomId);
@ -139,7 +142,7 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith
* @returns the fetchedData in base64
*/
export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithImageID) {
const fetched = await sogsV3FetchPreview(roomInfos);
const fetched = await sogsV3FetchPreview(roomInfos, true); // left pane are session official default rooms, which do require blinded
if (fetched && fetched.byteLength) {
return callUtilsWorker('arrayBufferToStringBase64', fetched);
}
@ -155,7 +158,8 @@ export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithIma
* Those default rooms do not have a conversation associated with them, as they are not joined yet
*/
const sogsV3FetchPreview = async (
roomInfos: OpenGroupV2RoomWithImageID
roomInfos: OpenGroupV2RoomWithImageID,
blinded: boolean
): Promise<Uint8Array | null> => {
if (!roomInfos || !roomInfos.imageID) {
return null;
@ -164,11 +168,10 @@ const sogsV3FetchPreview = async (
// not a batch call yet as we need to exclude headers for this call for now
const fetched = await fetchBinaryFromSogsWithOnionV4({
abortSignal: new AbortController().signal,
blinded: false,
blinded,
headers: null,
serverPubkey: roomInfos.serverPublicKey,
serverUrl: roomInfos.serverUrl,
doNotIncludeOurSogsHeaders: true,
roomId: roomInfos.roomId,
fileId: roomInfos.imageID,
throwError: false,
@ -198,7 +201,6 @@ export const sogsV3FetchFileByFileID = async (
headers: null,
serverPubkey: roomInfos.serverPublicKey,
serverUrl: roomInfos.serverUrl,
doNotIncludeOurSogsHeaders: true,
roomId: roomInfos.roomId,
fileId,
throwError: true,

@ -11,7 +11,7 @@ import {
export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => {
const result = await OnionSending.sendJsonViaOnionV4ToSogs({
blinded: false,
blinded: true,
endpoint: '/rooms',
method: 'GET',
serverPubkey: roomInfos.serverPublicKey,
@ -19,7 +19,6 @@ export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => {
abortSignal: new AbortController().signal,
serverUrl: roomInfos.serverUrl,
headers: null,
doNotIncludeOurSogsHeaders: true,
throwErrors: false,
});
@ -91,7 +90,6 @@ export async function openGroupV2GetRoomInfoViaOnionV4({
stringifiedBody: null,
serverPubkey,
headers: null,
doNotIncludeOurSogsHeaders: true,
throwErrors: false,
});
const room = result?.body as Record<string, any> | undefined;

@ -146,9 +146,7 @@ export interface SnodeFromSeed {
}
const getSnodeListFromSeednodeOneAtAtime = async (seedNodes: Array<string>) =>
allowOnlyOneAtATime('getSnodeListFromSeednode', () =>
getSnodeListFromSeednode(seedNodes)
) as Promise<Array<SnodeFromSeed>>;
allowOnlyOneAtATime('getSnodeListFromSeednode', () => getSnodeListFromSeednode(seedNodes));
/**
* This call will try 4 times to contact a seed nodes (random) and get the snode list from it.

@ -338,6 +338,13 @@ async function processAnyOtherErrorOnPath(
if (status !== 200) {
window?.log?.warn(`[path] Got status: ${status}`);
if (status === 404 || status === 400) {
window?.log?.warn(
'processAnyOtherErrorOnPathgot 404 or 400, probably a dead sogs. Skipping bad path update'
);
return;
}
// If we have a specific node in fault we can exclude just this node.
if (ciphertext?.startsWith(NEXT_NODE_NOT_FOUND_PREFIX)) {
const nodeNotFound = ciphertext.substr(NEXT_NODE_NOT_FOUND_PREFIX.length);

@ -277,7 +277,6 @@ async function sendJsonViaOnionV4ToSogs(sendOptions: {
method: string;
stringifiedBody: string | null;
abortSignal: AbortSignal;
doNotIncludeOurSogsHeaders?: boolean;
headers: Record<string, any> | null;
throwErrors: boolean;
}): Promise<OnionV4JSONSnodeResponse | null> {
@ -290,22 +289,19 @@ async function sendJsonViaOnionV4ToSogs(sendOptions: {
stringifiedBody,
abortSignal,
headers: includedHeaders,
doNotIncludeOurSogsHeaders,
throwErrors,
} = sendOptions;
if (!endpoint.startsWith('/')) {
throw new Error('endpoint needs a leading /');
}
const builtUrl = new URL(`${serverUrl}${endpoint}`);
let headersWithSogsHeadersIfNeeded = doNotIncludeOurSogsHeaders
? {}
: await OpenGroupPollingUtils.getOurOpenGroupHeaders(
serverPubkey,
endpoint,
method,
blinded,
stringifiedBody
);
let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
serverPubkey,
endpoint,
method,
blinded,
stringifiedBody
);
if (!headersWithSogsHeadersIfNeeded) {
return null;

@ -92,6 +92,18 @@ const decodeV4Response = (snodeResponse: SnodeResponseV4): DecodedResponseV4 | u
break;
case 'application/octet-stream':
break;
case 'text/html; charset=utf-8':
try {
window?.log?.warn(
'decodeV4Response - received raw body of type "text/html; charset=utf-8": ',
to_string(bodyBinary)
);
} catch (e) {
window?.log?.warn(
'decodeV4Response - received raw body of type "text/html; charset=utf-8" but not a string'
);
}
break;
default:
window?.log?.warn(
'decodeV4Response - No or unknown content-type information for response: ',

@ -17,11 +17,11 @@ export class TaskTimedOutError extends Error {
// one action resolves all
const oneAtaTimeRecord: Record<string, Promise<any>> = {};
export async function allowOnlyOneAtATime(
export async function allowOnlyOneAtATime<T>(
name: string,
process: () => Promise<any>,
process: () => Promise<T | undefined>,
timeoutMs?: number
) {
): Promise<T> {
// if currently not in progress
if (oneAtaTimeRecord[name] === undefined) {
// set lock
@ -37,7 +37,7 @@ export async function allowOnlyOneAtATime(
}, timeoutMs);
}
// do actual work
let innerRetVal;
let innerRetVal: T | undefined;
try {
innerRetVal = await process();
} catch (e) {

@ -364,7 +364,7 @@ export const _getLeftPaneLists = (
}
if (
unreadCount < 9 &&
unreadCount < 100 &&
conversation.unreadCount &&
conversation.unreadCount > 0 &&
conversation.currentNotificationSetting !== 'disabled'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -10,6 +10,7 @@ import {
waitForReadableMessageWithText,
waitForTestIdWithText,
} from './utils';
import { sleepFor } from '../../session/utils/Promise';
let windows: Array<Page> = [];
test.beforeEach(beforeAllClean);
@ -50,7 +51,9 @@ test('Disappearing Messages', async () => {
'readable-message',
'You set the disappearing message timer to 5 seconds'
);
await sleepFor(2000);
// Check top right hand corner indicator
await waitForTestIdWithText(windowA, 'disappearing-messages-indicator', '5 seconds');
// Send message
// Wait for tick of confirmation
@ -87,7 +90,7 @@ test('Disappearing Messages', async () => {
`${userA.userName} set the disappearing message timer to 5 seconds`
);
// Wait 5 seconds
await waitForMatchingText(windowB, `${userA.userName} disabled disappearing messages`);
await waitForMatchingText(windowB, `${userA.userName} has turned off disappearing messages.`);
// verify message is deleted in windowB
const errorDesc2 = 'Should not be found';
try {

@ -1,4 +1,5 @@
import { _electron, Page, test } from '@playwright/test';
import { sleepFor } from '../../session/utils/Promise';
import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach';
import { newUser } from './setup/new_user';
import { openAppAndWait } from './setup/open';
@ -36,42 +37,40 @@ test.describe('Password checks', () => {
await clickOnTestIdWithText(window, 'set-password-button');
// Enter password
await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Confirm password
await typeIntoInput(window, 'password-input-confirm', testPassword);
await window.keyboard.press('Delete');
// Click OK
await clickOnMatchingText(window, 'OK');
// await window.keyboard.press('Enter');
// Click Done
await clickOnMatchingText(window, 'Done');
// Check toast notification
await waitForTestIdWithText(
window,
'session-toast',
'Your password has been set. Please keep it safe'
'Your password has been set. Please keep it safe.'
);
// Click on settings tab
await sleepFor(300);
await clickOnTestIdWithText(window, 'settings-section');
// Type password into input field
await typeIntoInput(window, 'password-input', testPassword);
// Click OK
await clickOnMatchingText(window, 'OK');
// Click Done
await clickOnMatchingText(window, 'Done');
await clickOnTestIdWithText(window, 'settings-section');
// Change password
await clickOnMatchingText(window, 'Change Password');
await clickOnTestIdWithText(window, 'change-password-settings-button', 'Change Password');
console.warn('clicked Change Password');
// Enter old password
await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Enter new password
await typeIntoInput(window, 'password-input-confirm', newTestPassword);
await window.keyboard.press('Delete');
// await window.fill('#password-modal-input-confirm', newTestPassword);
await window.keyboard.press('Tab');
// Confirm new password
await typeIntoInput(window, 'password-input-reconfirm', newTestPassword);
await window.keyboard.press('Delete');
// await window.fill('#password-modal-input-reconfirm', newTestPassword);
// Press enter on keyboard
await window.keyboard.press('Enter');
// Select OK
await clickOnMatchingText(window, 'OK');
// Check toast notification for 'changed password'
await waitForTestIdWithText(
window,
@ -92,36 +91,44 @@ test.describe('Password checks', () => {
await clickOnMatchingText(window, 'Set Password');
// Enter password
await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Confirm password
await typeIntoInput(window, 'password-input-confirm', testPassword);
await window.keyboard.press('Delete');
// Click OK
// Click Done
await window.keyboard.press('Enter');
// // Click on settings tab
await sleepFor(100);
await clickOnTestIdWithText(window, 'settings-section');
// Type password into input field
await sleepFor(100);
await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Click OK
await clickOnMatchingText(window, 'OK');
// Navigate away from settings tab
// Click Done
await clickOnMatchingText(window, 'Done');
await sleepFor(100);
await window.mouse.click(0, 0);
await clickOnTestIdWithText(window, 'message-section');
await sleepFor(100);
// // Click on settings tab
await sleepFor(1000);
await clickOnTestIdWithText(window, 'settings-section');
// // Try with incorrect password
await typeIntoInput(window, 'password-input', '0000');
await window.keyboard.press('Delete');
await typeIntoInput(window, 'password-input', '000000');
// Confirm
await clickOnMatchingText(window, 'OK');
await clickOnMatchingText(window, 'Done');
// // invalid password banner showing?
await waitForMatchingText(window, 'Invalid password');
// // Empty password
// // Navigate away from settings tab
await window.mouse.click(0, 0);
await sleepFor(100);
await clickOnTestIdWithText(window, 'message-section');
await sleepFor(100);
// // Click on settings tab
await clickOnTestIdWithText(window, 'settings-section');
// // No password entered
await clickOnMatchingText(window, 'OK');
await clickOnMatchingText(window, 'Done');
// // Banner should ask for password to be entered
await waitForMatchingText(window, 'Please enter your password');
await waitForMatchingText(window, 'Enter password');
});
});

@ -15,15 +15,17 @@ test('Switch themes', async () => {
windows = windowLoggedIn.windows;
const [windowA] = windows;
// Check light theme colour is correct
const lightThemeColor = windowA.locator('.inbox.index');
await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)');
const darkThemeColor = windowA.locator('.inbox.index');
await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)');
// Click theme button and change to dark theme
await clickOnTestIdWithText(windowA, 'theme-section');
// Check background colour of background to verify dark theme
const darkThemeColor = windowA.locator('.inbox.index');
await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(23, 23, 23)');
const lightThemeColor = windowA.locator('.inbox.index');
await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)');
// Toggle back to light theme
await clickOnTestIdWithText(windowA, 'theme-section');
// Check background colour again
await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)');
await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)');
});

@ -28,15 +28,17 @@ export async function waitForMatchingText(window: Page, text: string) {
}
export async function clickOnMatchingText(window: Page, text: string, rightButton = false) {
console.info(`clickOnMatchingText: "${text}"`);
return window.click(`"${text}"`, rightButton ? { button: 'right' } : undefined);
}
export async function clickOnTestIdWithText(window: Page, dataTestId: string, text?: string) {
if (text) {
return window.click(`css=[data-testid=${dataTestId}]:has-text("${text}")`);
}
console.info(`clickOnTestIdWithText with testId:${dataTestId} and text:${text ? text : 'none'}`);
const builtSelector = !text
? `css=[data-testid=${dataTestId}]`
: `css=[data-testid=${dataTestId}]:has-text("${text}")`;
const builtSelector = `css=[data-testid=${dataTestId}]`;
await window.waitForSelector(builtSelector);
return window.click(builtSelector);
}
@ -46,6 +48,7 @@ export function getMessageTextContentNow() {
}
export async function typeIntoInput(window: Page, dataTestId: string, text: string) {
console.info(`typeIntoInput testId: ${dataTestId} : "${text}"`);
const builtSelector = `css=[data-testid=${dataTestId}]`;
return window.fill(builtSelector, text);
}

@ -46,7 +46,13 @@ export async function start(
}, 1000 * 60 * 10); // trigger and try to update every 10 minutes to let the file gets downloaded if we are updating
stopped = false;
await checkForUpdates(getMainWindow, messages, logger);
global.setTimeout(async () => {
try {
await checkForUpdates(getMainWindow, messages, logger);
} catch (error) {
logger.error('auto-update: error:', getPrintableError(error));
}
}, 2 * 60 * 1000); // we do checks from the fileserver every 1 minute.
}
export function stop() {

Loading…
Cancel
Save