@camunda/e2e-test-suite 0.0.1217 → 0.0.1219

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,7 @@ export declare function isSignedOut(page: Page): boolean;
22
22
  export declare function withSessionRecovery(page: Page, loginPage: LoginPage, appsPage: AppsPage, modelerBanner: Locator, testUser: {
23
23
  username: string;
24
24
  password: string;
25
- }, loginStagger: number, action: () => Promise<void>): Promise<void>;
25
+ }, loginStagger: number, action: () => Promise<void>, maxRecoveries?: number): Promise<void>;
26
26
  export declare function modelDiagramFromFile(page: Page, modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, processName: string, fileName?: string, nrOfRenamedUserTasks?: number, taskName?: string): Promise<void>;
27
27
  export declare function modelRestConnector(modelerCreatePage: ModelerCreatePage, connectorSettingsPage: ConnectorSettingsPage, processName: string, url: string, auth: string, resultExpression: string, resultVariable?: string, basicAuthCredentials?: {
28
28
  username: string;
@@ -235,7 +235,7 @@ function isSignedOut(page) {
235
235
  }
236
236
  exports.isSignedOut = isSignedOut;
237
237
  // Runs `action`, and if it fails because the session was dropped mid-flow,
238
- // signs back in, returns to the Modeler projects list and runs it once more.
238
+ // signs back in, returns to the Modeler projects list and runs it again.
239
239
  //
240
240
  // Without this the caller's own locator retries keep hunting for app controls
241
241
  // on the Auth0 form and expire against a page that is not the app at all —
@@ -243,21 +243,32 @@ exports.isSignedOut = isSignedOut;
243
243
  // than as the sign-out it actually was. Only a confirmed sign-out is retried;
244
244
  // any other failure propagates untouched, so a genuinely broken control still
245
245
  // fails the test.
246
- async function withSessionRecovery(page, loginPage, appsPage, modelerBanner, testUser, loginStagger, action) {
247
- try {
248
- await action();
249
- return;
250
- }
251
- catch (error) {
252
- if (!isSignedOut(page)) {
253
- throw error;
246
+ //
247
+ // Recovers up to `maxRecoveries` times rather than once: under a concurrent
248
+ // login burst the SaaS `/api/internal/login` hand-off can 500 and bounce the
249
+ // tab back to Auth0 (see AppsPage.openModelerWithRetry) more than once in a
250
+ // row, so the lowest-staggered workers get dropped, recovered, then dropped
251
+ // again — spending the single recovery before landing a stable session. A
252
+ // bounded re-login loop (the same shape as loginWithRetry's own retries) lets
253
+ // those tail sessions settle; a session that never stabilises still exhausts
254
+ // the loop and fails, so a genuinely broken login is not masked.
255
+ async function withSessionRecovery(page, loginPage, appsPage, modelerBanner, testUser, loginStagger, action, maxRecoveries = 2) {
256
+ for (let recovery = 0; recovery <= maxRecoveries; recovery++) {
257
+ try {
258
+ await action();
259
+ return;
260
+ }
261
+ catch (error) {
262
+ if (!isSignedOut(page) || recovery === maxRecoveries) {
263
+ throw error;
264
+ }
265
+ console.warn(`Session was dropped mid-flow (tab landed on ${page.url()}); ` +
266
+ `signing back in and retrying (recovery ${recovery + 1} of ` +
267
+ `${maxRecoveries}).`);
254
268
  }
255
- console.warn(`Session was dropped mid-flow (tab landed on ${page.url()}); ` +
256
- 'signing back in and retrying once.');
269
+ await loginWithRetry(page, loginPage, testUser, loginStagger);
270
+ await appsPage.openModelerWithRetry(modelerBanner);
257
271
  }
258
- await loginWithRetry(page, loginPage, testUser, loginStagger);
259
- await appsPage.openModelerWithRetry(modelerBanner);
260
- await action();
261
272
  }
262
273
  exports.withSessionRecovery = withSessionRecovery;
263
274
  async function modelDiagramFromFile(page, modelerHomePage, modelerCreatePage, processName, fileName = '', nrOfRenamedUserTasks = 0, taskName = '') {
@@ -12,6 +12,7 @@ const apiHelpers_1 = require("../../utils/apiHelpers");
12
12
  const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
13
13
  const users_1 = require("../../utils/users");
14
14
  const connectorSecrets_1 = require("../../utils/connectorSecrets");
15
+ const constants_1 = require("../../utils/constants");
15
16
  const testUser = (0, users_1.getTestUser)('seventeenthUser');
16
17
  _8_10_1.test.describe.configure({ mode: 'parallel' });
17
18
  _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
@@ -271,19 +272,21 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
271
272
  await operateHomePage.clickProcessesTab();
272
273
  await operateProcessesPage.clickProcessCompletedCheckbox();
273
274
  await operateProcessesPage.clickProcessInstanceLink(processName);
274
- await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(page, operateProcessInstancePage.completedIcon, {
275
- // This flow's REST connector call round-trips to a real AWS
276
- // endpoint using the secrets above, which can take longer to
277
- // complete than the suite's default 60s/3-retry budget (seen
278
- // timing out in run 35340608406). Match the operateBanner budget
279
- // above rather than the suite default used elsewhere.
280
- totalTimeout: 120000,
281
- visibilityTimeout: 30000,
282
- maxRetries: 5,
283
- postAction: async () => {
284
- await page.reload();
285
- },
286
- });
275
+ // Poll the instance deep link rather than reloading whatever page the
276
+ // tab is left on, the same way the identical processes-list flow in
277
+ // smoke-tests.spec.ts does. The completed filter also lists still-ACTIVE
278
+ // instances, so the link is regularly opened before this flow's REST
279
+ // connector has finished round-tripping to a real AWS endpoint, and
280
+ // expectLocatorWithRetry is blind to both that and to Operate's 404
281
+ // bounce: it reported only "Failed to assert visibility locator ...
282
+ // after 4 attempts" (run 35595548687) without saying which state the
283
+ // header had reached. waitForInstanceLocator re-asks Operate every few
284
+ // seconds within the same budget the other import gates use, names the
285
+ // rendered state on timeout, and fails immediately with the incident
286
+ // text when the connector job raised one -- COMPLETED is unreachable
287
+ // then, so waiting out the budget only hides the cause.
288
+ await operateProcessInstancePage.rememberInstanceUrl(constants_1.TIMEOUT.navigation);
289
+ await operateProcessInstancePage.waitForInstanceLocator(operateProcessInstancePage.completedIcon, 'Completed icon', constants_1.TIMEOUT.processImport);
287
290
  await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(page, operateProcessInstancePage.variablesList, {
288
291
  postAction: async () => {
289
292
  await page.reload();
@@ -177,11 +177,12 @@ _8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-aler
177
177
  });
178
178
  (0, _8_10_1.test)('Enable dashboard sharing and expose a public share link', async ({ appsPage, optimizeHomePage, optimizeSharePage, }) => {
179
179
  await _8_10_1.test.step('Open Optimize from the Camunda apps launcher', async () => {
180
- await appsPage.clickCamundaApps();
181
- await appsPage.clickOptimize(clusterName);
182
- await test_1.expect
183
- .poll(async () => optimizeHomePage.optimizeBanner.isVisible({ timeout: 5000 }), { timeout: 120000 })
184
- .toBe(true);
180
+ // Same retrying opener as the two tests above, for the same reason: a
181
+ // /sso-callback that answers 401 leaves the tab on a blank page that
182
+ // never renders the banner, and polling it can only watch that dead
183
+ // page time out (`expect(received).toBe(expected)` after 120s). Only
184
+ // re-entering through the apps menu starts a fresh hand-off.
185
+ await appsPage.openOptimizeWithRetry(optimizeHomePage.optimizeBanner, clusterName);
185
186
  });
186
187
  await _8_10_1.test.step('Navigate to the seeded dashboard', async () => {
187
188
  await optimizeSharePage.openEntityById(baseUrl, 'dashboard', dashboardId);
@@ -740,6 +740,28 @@ async function probeOptimizeSession(page) {
740
740
  }
741
741
  return token;
742
742
  }
743
+ // Starts a fresh OIDC code exchange with Optimize without touching the
744
+ // identity provider session. The 401 that probeOptimizeSession catches is the
745
+ // cluster rejecting one exchange, not the IdP rejecting the user: the Auth0
746
+ // session minted moments earlier is still valid, so navigating to Optimize
747
+ // again sends the browser back through the IdP, which answers from that
748
+ // session and returns a brand new code. That costs seconds and no Auth0
749
+ // credential round trip, where a full re-login costs another interactive
750
+ // sign-in plus the minutes of backoff that precede it -- and there are only
751
+ // four of those before the caller's beforeAll fails outright with
752
+ // OPTIMIZE_SESSION_UNAUTHENTICATED.
753
+ async function redoOptimizeSsoHandshake(page) {
754
+ const optimizeUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
755
+ await page.goto(optimizeUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
756
+ // The redirect chain has to land back on Optimize for the exchange to have
757
+ // completed. A chain that stops anywhere else (the IdP asking for
758
+ // credentials again, another rejected callback) throws a waitForURL
759
+ // timeout, which the caller's catch already classifies and backs off for.
760
+ await page.waitForURL((url) => url.toString().startsWith(optimizeUrl), {
761
+ timeout: 60000,
762
+ waitUntil: 'commit',
763
+ });
764
+ }
743
765
  async function performOptimizeInteractiveLogin(page, callerLabel, verifySession) {
744
766
  const optimizeUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
745
767
  const maxLoginAttempts = 4;
@@ -764,7 +786,15 @@ async function performOptimizeInteractiveLogin(page, callerLabel, verifySession)
764
786
  await page.context().clearCookies();
765
787
  await (0, utils_1.loginToApp)(optimizeUrl, page);
766
788
  if (verifySession && !(await verifySession(page))) {
767
- throw new Error(OPTIMIZE_SESSION_UNAUTHENTICATED);
789
+ // Redo the hand-off once before writing the attempt off: the rejected
790
+ // exchange is what has to be replaced, and re-navigating replaces it
791
+ // for seconds (see redoOptimizeSsoHandshake). Only when a second,
792
+ // freshly minted exchange is also unauthenticated is the login itself
793
+ // worth repeating.
794
+ await redoOptimizeSsoHandshake(page);
795
+ if (!(await verifySession(page))) {
796
+ throw new Error(OPTIMIZE_SESSION_UNAUTHENTICATED);
797
+ }
768
798
  }
769
799
  return;
770
800
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1217",
3
+ "version": "0.0.1219",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",