Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -774,8 +774,8 @@ export class ComposeRecipientsModule extends ViewModule<ComposeView> {
};

private renderSearchRes = async (input: JQuery, contacts: ContactPreview[], query: ProviderContactsQuery) => {
if (!input.is(':focus')) {
// focus was moved away from input
if (input.get(0) !== document.activeElement) {
// Browser-level focus may move to an OAuth popup while the recipient input remains active in this document.
return;
}
if ((input.val() as string).toLowerCase() !== query.substring.toLowerCase()) {
Expand Down
456 changes: 167 additions & 289 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@tony.ganchev/eslint-plugin-header": "3.4.4",
"@types/chai": "5.2.3",
"@types/chai-as-promised": "8.0.2",
"@types/chrome": "0.2.2",
"@types/chrome": "0.2.5",
"@types/fs-extra": "11.0.4",
"@types/jquery": "4.0.1",
"@types/mailparser": "3.4.6",
Expand All @@ -22,7 +22,7 @@
"cross-env": "10.1.0",
"eslint": "10.8.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-jsdoc": "63.3.1",
"eslint-plugin-jsdoc": "63.3.3",
"eslint-plugin-local-rules": "3.0.2",
"eslint-plugin-no-null": "1.0.2",
"eslint-plugin-no-only-tests": "3.4.0",
Expand All @@ -31,22 +31,22 @@
"globby": "16.2.2",
"googleapis": "173.0.0",
"husky": "9.1.7",
"lint-staged": "17.2.0",
"lint-staged": "17.3.0",
"mailparser": "3.9.14",
"mkdirp": "3.0.1",
"openpgp": "6.3.1",
"pdfjs-dist": "6.1.200",
"pdfjs-dist": "6.2.108",
"postcss-html": "1.8.1",
"prettier": "3.9.6",
"puppeteer": "25.1.0",
"puppeteer": "25.4.0",
"stylelint": "17.14.1",
"stylelint-config-standard": "40.0.0",
"tap-xunit": "2.4.1",
"typescript": "npm:@typescript/typescript6@^6.0.3",
"typescript-eslint": "8.65.0",
"undici-types": "8.9.0",
"web-ext": "10.5.0",
"webpack-cli": "7.2.1"
"webpack-cli": "7.2.2"
},
"dependencies": {
"@flowcrypt/fine-uploader": "5.16.4",
Expand Down
1 change: 1 addition & 0 deletions test/source/browser/browser-handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class BrowserHandle {
});
}
await page.setViewport(this.viewport);
await page.bringToFront();
const controllablePage = new ControllablePage(t, page);
if (url) {
if (initialScript) {
Expand Down
65 changes: 49 additions & 16 deletions test/source/tests/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
testWithBrowser(async (t, browser) => {
const primarySignature = 'Test primary signature';
const aliasSignature = 'Test alias signature';
const acctAliases = [{
...flowcryptCompatibilityAliasList[0],
signature: aliasSignature,
}];
const acctAliases = [
{
...flowcryptCompatibilityAliasList[0],
signature: aliasSignature,
},
];
await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility', {
google: { acctAliases, acctPrimarySignature: primarySignature },
attester: { includeHumanKey: true },
Expand Down Expand Up @@ -1677,8 +1679,8 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
const oauthPopup = await browser.newPageTriggeredBy(t, () => composePage.waitAndClick('@action-auth-with-contacts-scope'));
await OauthPageRecipe.google(t, oauthPopup, acct, 'approve');
}
await Util.sleep(3);
await ComposePageRecipe.expectContactsResultEqual(composePage, ['contact.test@flowcrypt.com']);
await composePage.page.bringToFront();
// re-load the compose window, expect that it remembers scope was connected, and remembers the contact
composePage = await ComposePageRecipe.openStandalone(t, browser, 'compose');
await composePage.waitAndClick('@action-show-cc');
Expand Down Expand Up @@ -1710,15 +1712,28 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
await Util.sleep(2);
await ComposePageRecipe.showRecipientInput(composePage);
await composePage.type('@input-to', 'contact');
const loadingIconSelector = '[data-test="pgp-loading-icon"]';
const waitForLoadingIcon = (timeout: number) =>
composePage.target.waitForFunction(
selector => {
const loadingIcon = document.querySelector<HTMLElement>(selector);
return Boolean(loadingIcon?.offsetWidth && loadingIcon.offsetHeight);
},
{ polling: 'mutation', timeout },
loadingIconSelector
);
if (testVariant === 'CONSUMER-MOCK') {
// allow contacts scope
const loadingIconAppeared = waitForLoadingIcon(90_000);
// Popup creation can fail before Promise.all observes this waiter.
void loadingIconAppeared.catch(() => undefined);
const oauthPopup = await browser.newPageTriggeredBy(t, () => composePage.waitAndClick('@action-auth-with-contacts-scope'));
await OauthPageRecipe.google(t, oauthPopup, account, 'approve');
await Promise.all([loadingIconAppeared, OauthPageRecipe.google(t, oauthPopup, account, 'approve')]);
await composePage.page.bringToFront();
} else {
await waitForLoadingIcon(20_000);
}
await Util.sleep(1);
await composePage.waitAll('@pgp-loading-icon');
await Util.sleep(3); // Wait for 3 seconds to allow PGP status update and loading icon to disappear
await composePage.notPresent('@pgp-loading-icon');
await composePage.target.waitForFunction(selector => !document.querySelector(selector), { polling: 'mutation', timeout: 20_000 }, loadingIconSelector);
})
);

Expand Down Expand Up @@ -1922,8 +1937,6 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
})
);



test(
'compose - check existing draft not saved without changes',
testWithBrowser(async (t, browser) => {
Expand Down Expand Up @@ -2185,8 +2198,27 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
// focus the 1st one
const firstFrameId = /frameId=.*?&/s.exec(framesUrls[0])![0];
const firstComposeFrame = await inboxPage.getFrame(['compose.htm', firstFrameId]);
await inboxPage.waitAndFocus('iframe');
await firstComposeFrame.waitAndFocus('@input-body');
await inboxPage.waitAndFocus('.secure_compose_window[data-order="1"] iframe');
await firstComposeFrame.waitAndClick('@input-body');
await inboxPage.target.waitForFunction(
() => {
const firstCompose = document.querySelector('.secure_compose_window[data-order="1"]');
const secondCompose = document.querySelector('.secure_compose_window[data-order="2"]');
const thirdCompose = document.querySelector('.secure_compose_window[data-order="3"]');
if (!firstCompose || !secondCompose || !thirdCompose) {
return false;
}
return (
firstCompose.classList.contains('active') &&
!firstCompose.classList.contains('previous_active') &&
!secondCompose.classList.contains('active') &&
!secondCompose.classList.contains('previous_active') &&
!thirdCompose.classList.contains('active') &&
thirdCompose.classList.contains('previous_active')
);
},
{ polling: 'mutation', timeout: 20_000 }
);
// make sure the 1st compose window is active, and the 3rd is previous_active
expect(await inboxPage.hasClass('.secure_compose_window[data-order="1"]', 'active')).to.be.true;
expect(await inboxPage.hasClass('.secure_compose_window[data-order="2"]', 'active')).to.be.false;
Expand Down Expand Up @@ -3511,8 +3543,9 @@ const sendTextAndVerifyPresentInSentMsg = async (
text: string,
sendingOpt: { encrypt?: boolean; sign?: boolean; richtext?: boolean } = {}
) => {
const subject = `Test Sending ${sendingOpt.sign ? 'Signed' : ''} ${sendingOpt.encrypt ? 'Encrypted' : ''
} Message With Test Text ${text} ${Util.lousyRandom()}`;
const subject = `Test Sending ${sendingOpt.sign ? 'Signed' : ''} ${
sendingOpt.encrypt ? 'Encrypted' : ''
} Message With Test Text ${text} ${Util.lousyRandom()}`;
const composePage = await ComposePageRecipe.openStandalone(t, browser, 'compatibility');
await ComposePageRecipe.fillMsg(composePage, { to: 'human@flowcrypt.com' }, subject, text, sendingOpt);
const acctEmail = 'flowcrypt.compatibility@gmail.com';
Expand Down
10 changes: 5 additions & 5 deletions test/source/tests/decrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1673,16 +1673,16 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
'decrypt - public key is rendered minimized for outgoing messages',
testWithBrowser(async (t, browser) => {
const assertOutgoingPubkeyFrameIsMinimized = async (page: ControllablePage) => {
await page.waitAll('iframe.pgp_block.publicKey', { timeout: 30 });
const pubkeyFrame = await page.getFrame(['pgp_pubkey.htm', 'minimized=___cu_true___'], { timeout: 30 });
await pubkeyFrame.waitForContent('@container-pgp-pubkey', 'Public Key', 30);
await pubkeyFrame.target.waitForFunction(
() => {
const addContactButton = document.querySelector<HTMLElement>('[data-test="action-add-contact"]');
return Boolean(addContactButton && !addContactButton.offsetHeight);
const pubkeyContainer = document.querySelector<HTMLElement>('[data-test="container-pgp-pubkey"]');
const addContactLine = document.querySelector<HTMLElement>('.line.add_contact');
return Boolean(pubkeyContainer?.textContent?.includes('Public Key') && addContactLine?.style.display === 'none');
},
{ timeout: 30_000 }
{ polling: 'mutation', timeout: 30_000 }
);
expect(await pubkeyFrame.isElementVisible('@action-add-contact')).to.be.false; // hidden because sender matches acctEmail
};

const { acctEmail, authHdr } = await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'ci.tests.gmail');
Expand Down
7 changes: 4 additions & 3 deletions test/source/tests/flaky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
'confirm',
'cancel',
'Messages to some recipients were sent successfully, while messages to flowcrypt.compatibility@gmail.com, Mr Cc <cc@example.com> ' +
'encountered error(s) from Gmail. Please help us improve FlowCrypt by reporting the error to us.'
'encountered error(s) from Gmail. Please help us improve FlowCrypt by reporting the error to us.'
);
await composePage.close();
expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(++expectedNumberOfPassedMessages);
Expand All @@ -350,7 +350,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
'error',
'confirm',
'Messages to some recipients were sent successfully, while messages to invalid@example.com ' +
'encountered error(s) from Gmail: Invalid recipients\n\nPlease remove recipients, add them back and re-send the message.'
'encountered error(s) from Gmail: Invalid recipients\n\nPlease remove recipients, add them back and re-send the message.'
);
await composePage.close();
expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(++expectedNumberOfPassedMessages);
Expand All @@ -375,7 +375,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
'error',
'confirm',
'Messages to some recipients were sent successfully, while messages to timeout@example.com ' +
'encountered network errors. Please check your internet connection and try again.'
'encountered network errors. Please check your internet connection and try again.'
);
await composePage.close();
expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(++expectedNumberOfPassedMessages);
Expand Down Expand Up @@ -568,6 +568,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
'testsearchorder7@flowcrypt.com',
'testsearchorder8@flowcrypt.com',
]);
await inboxPage.page.bringToFront();
await composeFrame.waitAndClick('@action-close-new-message');
await inboxPage.waitTillGone('@container-new-message');
// add key + send
Expand Down
22 changes: 16 additions & 6 deletions test/source/tests/page-recipe/compose-page-recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,22 @@ export class ComposePageRecipe extends PageRecipe {
};

public static expectContactsResultEqual = async (composePage: ControllablePage | ControllableFrame, emails: string[]) => {
await Util.sleep(5);
const contacts = await composePage.waitAny('@container-contacts');
const contactsList = await contacts.$$('li');
for (const [index, contact] of contactsList.entries()) {
expect(await PageRecipe.getElementPropertyJson(contact, 'textContent')).to.equal(emails[index]);
}
const contactsSelector = '[data-test="container-contacts"]';
const result = await composePage.target.waitForFunction(
(selector, expectedEmails) => {
const contactsContainer = document.querySelector<HTMLElement>(selector);
if (!contactsContainer?.offsetWidth || !contactsContainer.offsetHeight) {
return false;
}
const actualEmails = Array.from(contactsContainer.querySelectorAll('li'), contact => contact.textContent);
return actualEmails.length === expectedEmails.length && actualEmails.every((email, index) => email === expectedEmails[index]) ? actualEmails : false;
},
{ polling: 'mutation', timeout: 20_000 },
contactsSelector,
emails
);
expect(await result.jsonValue()).to.deep.equal(emails);
await result.dispose();
};

public static pastePublicKeyManuallyNoClose = async (composeFrame: ControllableFrame, inboxPage: ControllablePage, recipient: string, pub: string) => {
Expand Down
Loading