Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10349 Bug: Idle Logout Issues (with automated tests) #5176

Merged
merged 51 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
92ad46e
10349-bug: separate idle logout path from user logout path, fixing th…
Mwindo Jul 23, 2024
999ae68
10349-bug: fix race condition bugs, and clear the idle status timer o…
Mwindo Jul 23, 2024
139c908
10349-bug: add some unit tests
Mwindo Jul 23, 2024
8189d1e
10349-bug: pull out constants
Mwindo Jul 23, 2024
dcc55bb
10349-bug: better constants
Mwindo Jul 23, 2024
4d56e6a
10349-bug: remove unnecessary action
Mwindo Jul 23, 2024
661e406
10349-bug: undoing routing behavior of idle logout to preserve curren…
Mwindo Jul 24, 2024
4d92595
add user signed in check for idle-logout route
Mwindo Jul 24, 2024
2383922
10349-bug: get multi-tab test working
Mwindo Jul 26, 2024
52e7a8d
10349-bug: try adding a wait to fix GA failing test
Mwindo Jul 26, 2024
1d50fab
10349-bug: try a hack to see if test passes
Mwindo Jul 26, 2024
ef1e4d8
10349-bug: temporarily disable failing test to see if other test pass…
Mwindo Jul 26, 2024
bef73fd
10349-bug: re-add test, and temporarily set constants in getConstants…
Mwindo Jul 26, 2024
ac2c799
10349-bug: dismiss modals in older tabs to prevent surprise logouts i…
Mwindo Jul 26, 2024
d6c52e4
10349-bug: add some temporary logging to debug CI test failure since …
Mwindo Jul 26, 2024
1652f9e
10349-bug: undo logging, try removing render check in app.tsx to see …
Mwindo Jul 26, 2024
a7306da
10349-bug: try to get tests to not fail spectacularly
Mwindo Jul 26, 2024
5e7c342
Merge branch 'staging' into 10349-bug
Mwindo Jul 26, 2024
1745a0d
fix merge issue
Mwindo Jul 26, 2024
4ac3c96
10349-bug: add AppContext and AppInstanceManagerWrapper to allow us t…
Mwindo Jul 27, 2024
ffe5955
10349-bug: revert getConstants temp test hack
Mwindo Jul 27, 2024
e4f9575
10349-bug: fix unit test, try defaulting to true in appContext to see…
Mwindo Jul 27, 2024
029b57e
10349-bug: remove code that turned out to be unnecessary, focus on fa…
Mwindo Jul 27, 2024
6699bf5
10349-bug: forgot to remove ci check :/
Mwindo Jul 27, 2024
f293899
10349-bug: try using cypress baseUrl rather than localhost
Mwindo Jul 29, 2024
7959c0f
10349-bug: do some more logging, try reloading page (since it seems, …
Mwindo Jul 29, 2024
740cc5b
10349-bug: last attempt at logging the puppeteer CI failure
Mwindo Jul 29, 2024
d57373a
10349-bug: extend timeout
Mwindo Jul 29, 2024
1713615
10349-bug: one final *final* attempt, as I believe puppeteer page is …
Mwindo Jul 29, 2024
ae47b67
10349-bug: hail mary attempt before just giving up
Mwindo Jul 29, 2024
9c6fbfd
10349-bug: last try, for real
Mwindo Jul 29, 2024
e72877e
10349-bug: just had an idea about what the issue might be
Mwindo Jul 29, 2024
79dd8ee
10349-bug: clean up in case this actually works
Mwindo Jul 29, 2024
3e7f612
10349-bug: remove package that was causing a failure, and fix unit test
Mwindo Jul 29, 2024
eb3fcd2
10349-bug: update broadcastIdleStatusActiveAction.test.ts, remove con…
Mwindo Jul 29, 2024
2cd2b76
Merge remote-tracking branch 'ustc/staging' into 10349-bug
Mwindo Jul 29, 2024
8d37302
10349-bug: add missing test
Mwindo Jul 29, 2024
2b6f6ef
10349-bug: incorporate reviewer feedback
Mwindo Jul 30, 2024
e0988bb
10349-bug: remove unused import :/
Mwindo Jul 30, 2024
bb01bbe
10349-bug: expose cerebral in test and add better typing for sequences
Mwindo Aug 1, 2024
27a79a8
10349-bug: rename LOGOUT_BROADCAST_MESSAGES to BROADCAST_MESSAGE; add…
Mwindo Aug 9, 2024
f0039f5
Merge branch 'staging' into 10349-bug
Mwindo Aug 9, 2024
24760f6
10349-bug: stop tracking idle time if dawson has been updated, update…
Mwindo Aug 9, 2024
a0b540f
10349-bug: rename, and extract actions into a new sequence
Mwindo Aug 9, 2024
2eba0e4
10349-bug: update handleIdleLogoutAction
Mwindo Aug 12, 2024
ef4eed9
10349-bug: fix handleIdleLogoutAction.test.ts
Mwindo Aug 12, 2024
6fd5684
10349-bug: ensure idle logoutat is cleared on signing out
Mwindo Aug 15, 2024
d7d35b5
Merge branch 'staging' into 10349-bug
Mwindo Aug 15, 2024
1160002
Merge branch 'staging' into 10349-bug
Mwindo Aug 16, 2024
31a1f66
10349-bug: minor updates to tests
Mwindo Aug 16, 2024
8944515
Merge branch '10349-bug' of github.com:flexion/ef-cms into 10349-bug
Mwindo Aug 16, 2024
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
65 changes: 65 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ import {
getNewAccountVerificationCode,
toggleFeatureFlag,
} from './cypress/helpers/cypressTasks/dynamo/dynamo-helpers';
import { overrideIdleTimeouts } from './cypress/local-only/support/idleLogoutHelpers';
import { unzipFile } from './cypress/helpers/file/unzip-file';
import { waitForNoce } from './cypress/helpers/cypressTasks/wait-for-noce';
import { waitForPractitionerEmailUpdate } from './cypress/helpers/cypressTasks/wait-for-practitioner-email-update';

import type { Page } from 'puppeteer-core';

import { retry, setup } from '@cypress/puppeteer';

// eslint-disable-next-line import/no-default-export
export default defineConfig({
chromeWebSecurity: false,
Expand Down Expand Up @@ -77,6 +82,66 @@ export default defineConfig({
});
},
});
// Setup for puppeteer, which supports multi-tab tests
// Define your function in onMessage, and call it like cy.puppeteer('yourFunctionName', arg1, arg2 ...)
setup({
on,
onMessage: {
async closeTab(browser: any, url: string) {
const desiredPage = await retry<Promise<Page>>(async () => {
const pages = await browser.pages();
const page = pages.find(p => p.url().includes(url));
if (!page) throw new Error('Could not find page');
return page;
});
await desiredPage.close();
},
async openExistingTabAndCheckSelectorExists(
browser: any,
url: string,
selector: string,
close: boolean = true,
) {
// Note that browser.pages is *not* sorted in any particular order.
// Therefore we pass in the URL we want to find rather than an index.

// Wait until the new tab loads
const desiredPage = await retry<Promise<Page>>(async () => {
const pages = await browser.pages();
const page = pages.find(p => p.url().includes(url));
if (!page) throw new Error('Could not find page');
return page;
});

// Activate it
await desiredPage.bringToFront();

// Make sure selector exists
await desiredPage.waitForSelector(selector, { timeout: 30000 });

if (close) {
await desiredPage.close();
}
return true;
},
async openNewTab(
browser: any,
url: string,
sessionModalTimeout: number,
sessionTimeout: number,
) {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });

await page.evaluate(overrideIdleTimeouts, {
sessionModalTimeout,
sessionTimeout,
});

return page;
},
},
});
},
specPattern: 'cypress/local-only/tests/**/*.cy.ts',
supportFile: 'cypress/local-only/support/index.ts',
Expand Down
4 changes: 2 additions & 2 deletions cypress/CYPRESS-README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# Best Practices

In order to write a realiable cypress test suites, there are some best practices we should follow that our outlined in the cypress documentation and also some best practices we have learned from trying to write realiable tests.
In order to write a reliable cypress test suites, there are some best practices we should follow that our outlined in the cypress documentation and also some best practices we have learned from trying to write reliable tests.

## DO'S
- Access DOM elements using `data-testid selector`.
Expand All @@ -11,7 +11,7 @@ In order to write a realiable cypress test suites, there are some best practices
- Avoid cy.get('#my-id').
- Wait for actions to finish explicitly.
- Always verify something on the page after running an action or loading a new page. For example, if you click on a button which updates a practitioner name, be sure to wait for a success alert to appear before trying to move onto the next steps in your test. Failing to do this will result in race conditions and flaky tests.
- This is especially important for accessibilty tests, wait explicitly for the page to full load before running an accessibility scan. If you are seeing 'color-contrast' violations that are intermittent you are most likely not waiting for the right element to be loaded before running a scan.
- This is especially important for accessibility tests, wait explicitly for the page to full load before running an accessibility scan. If you are seeing 'color-contrast' violations that are intermittent you are most likely not waiting for the right element to be loaded before running a scan.
- Extract reusable steps.
- Try to find ways to create helper functions which we can re-use in other tests. For example, creating a case as a petitioner is a good re-usable flow. When writing these helpers, be sure they do not contain asserts related to the high level test you are writing. They should just login as a user, create or modify the data, then return any new created values we may need.
- Test should be re-runnable.
Expand Down
8 changes: 8 additions & 0 deletions cypress/helpers/ITestableWindow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// An interface for exposing the cerebral controller on the window object, which
// can be useful for temporarily overwriting constants in cypress.
export interface ITestableWindow {
cerebral: {
getState: () => any;
getModel: () => any;
};
}
19 changes: 19 additions & 0 deletions cypress/local-only/support/idleLogoutHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ITestableWindow } from '../../helpers/ITestableWindow';

// This is a hack, but I do not know a better way.
export const overrideIdleTimeouts = ({
modalTimeout,
sessionTimeout,
windowObj, // For native cypress, this needs to be defined. For the puppeteer plugin, it should be left blank.
}: {
modalTimeout: number;
sessionTimeout: number;
windowObj?: ITestableWindow;
}) => {
const currentWindow = windowObj || (window as unknown as ITestableWindow);
currentWindow.cerebral.getModel().set(['constants'], {
...currentWindow.cerebral.getState().constants,
SESSION_MODAL_TIMEOUT: modalTimeout,
SESSION_TIMEOUT: sessionTimeout,
});
};
1 change: 1 addition & 0 deletions cypress/local-only/support/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './commands';
import '@cypress/puppeteer/support';
import 'cypress-axe';

before(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { ITestableWindow } from '../../../../helpers/ITestableWindow';
import { loginAsColvin } from '../../../../helpers/authentication/login-as-helpers';
import { overrideIdleTimeouts } from '../../../support/idleLogoutHelpers';
import { retry } from '../../../../helpers/retry';

describe('Idle Logout Behavior', () => {
const DEFAULT_IDLE_TIMEOUT = 500;
it('should automatically log user out after refresh with option to log back in', () => {
loginAsColvin();
cy.reload(); // Refresh ensures we track idle time even without interaction on the page
Mwindo marked this conversation as resolved.
Show resolved Hide resolved
cy.get('[data-testid="header-text"]');
cy.window().then((window: Window) => {
overrideIdleTimeouts({
modalTimeout: DEFAULT_IDLE_TIMEOUT,
sessionTimeout: DEFAULT_IDLE_TIMEOUT,
windowObj: window as unknown as ITestableWindow,
});
});

retry(() => {
return cy.get('body').then(body => {
return body.find('[data-testid="idle-logout-login-button"]').length > 0;
});
});

cy.get('[data-testid="idle-logout-login-button"]').click();
cy.get('[data-testid="login-button"]').should('exist');
});

it('should close modal in other tab when loading new tab', () => {
loginAsColvin();
cy.get('[data-testid="header-text"]');
cy.window().then((window: Window) => {
overrideIdleTimeouts({
modalTimeout: 30000, // We want the modal to appear relatively quickly, but we do not want to sign out
sessionTimeout: 1000,
windowObj: window as unknown as ITestableWindow,
});
});

// Wait until modal is there
cy.get('[data-testid="are-you-still-there-modal"]').should('exist');

const newTabUrl = Cypress.config('baseUrl') + '/messages/my/inbox';
cy.puppeteer('openNewTab', newTabUrl);

// Then confirm opening a new tab closed the modal
cy.get('[data-testid="are-you-still-there-modal"]').should('not.exist');
cy.puppeteer('closeTab', newTabUrl);
});

it('should sign out of all tabs after idle', () => {
// Note that throughout this test, we interact with the first tab via cypress
// and all other tabs through the puppeteer plugin. Mixing this up will cause errors.

loginAsColvin();
const urls = [
'/messages/my/inbox',
'/document-qc/section/inbox',
'/trial-sessions',
];
urls.forEach(url => {
cy.puppeteer(
'openNewTab',
Cypress.config('baseUrl') + url,
DEFAULT_IDLE_TIMEOUT,
DEFAULT_IDLE_TIMEOUT,
);
});
cy.window().then((window: Window) => {
overrideIdleTimeouts({
modalTimeout: DEFAULT_IDLE_TIMEOUT,
sessionTimeout: DEFAULT_IDLE_TIMEOUT,
windowObj: window as unknown as ITestableWindow,
});
});

// We sync all the tabs to timeout at the same time by clicking, which broadcasts a "last active" time across tabs.
// They should all sign out at the same time.
cy.get('body').click();

cy.get('[data-testid="idle-logout-login-button"]').should('exist');
urls.forEach(url =>
cy.puppeteer(
'openExistingTabAndCheckSelectorExists',
url,
'[data-testid="idle-logout-login-button"]',
),
);
});
});
Loading
Loading