@camunda/e2e-test-suite 0.0.1231 → 0.0.1232
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.
- package/dist/pages/8.8/FormJsPage.js +5 -0
- package/dist/pages/8.8/ModelerCreatePage.js +13 -0
- package/dist/pages/8.8/ModelerHomePage.js +46 -3
- package/dist/pages/8.8/ProcessApplicationPage.js +12 -0
- package/dist/utils/sessionRecovery.d.ts +40 -0
- package/dist/utils/sessionRecovery.js +83 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ exports.FormJsPage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
7
|
+
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
7
8
|
class FormJsPage {
|
|
8
9
|
page;
|
|
9
10
|
processApplicationPage;
|
|
@@ -171,6 +172,10 @@ class FormJsPage {
|
|
|
171
172
|
});
|
|
172
173
|
}
|
|
173
174
|
async clickDeployButton() {
|
|
175
|
+
// ensureStageCluster navigates away and back before every deploy, so the
|
|
176
|
+
// action bar this button lives in may belong to a page that never
|
|
177
|
+
// rendered. Recover before spending the click budget on it.
|
|
178
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
174
179
|
await this.deployButton.click({ timeout: 60000 });
|
|
175
180
|
}
|
|
176
181
|
async clickDeploySubButton() {
|
|
@@ -6,6 +6,7 @@ const sleep_1 = require("../../utils/sleep");
|
|
|
6
6
|
const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
|
|
7
7
|
const ConnectorMarketplacePage_1 = require("./ConnectorMarketplacePage");
|
|
8
8
|
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
9
|
+
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
9
10
|
class ModelerCreatePage {
|
|
10
11
|
page;
|
|
11
12
|
processApplicationPage;
|
|
@@ -583,6 +584,12 @@ class ModelerCreatePage {
|
|
|
583
584
|
await (0, test_1.expect)(this.viewProcessLink).toBeHidden({ timeout: 30000 });
|
|
584
585
|
}
|
|
585
586
|
async clickStartInstanceMainButton() {
|
|
587
|
+
// ensureStageCluster runs immediately before this on every runProcessInstance
|
|
588
|
+
// attempt and navigates twice, so the diagram's action bar may belong to a
|
|
589
|
+
// page Modeler never rendered. Without this, each of the 10 retries burned
|
|
590
|
+
// 60s of probes here and the run surfaced as "View process instance" never
|
|
591
|
+
// appearing -- which reads as a failed deploy rather than a dead page.
|
|
592
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
586
593
|
if (await this.startInstanceMainButton.isVisible({ timeout: 30000 })) {
|
|
587
594
|
await this.startInstanceMainButton.click({ timeout: 30000 });
|
|
588
595
|
}
|
|
@@ -754,6 +761,11 @@ class ModelerCreatePage {
|
|
|
754
761
|
await this.nameInput.fill(name, { timeout: 60000 });
|
|
755
762
|
}
|
|
756
763
|
async clickDiagramBreadcrumb() {
|
|
764
|
+
// Reached straight after opening a diagram, so the navigation that got us
|
|
765
|
+
// here is the one Modeler can answer with its hung /login. The breadcrumb
|
|
766
|
+
// never renders there, and at 10s this was the first thing to notice --
|
|
767
|
+
// as "breadcrumb not found", which says nothing about the real cause.
|
|
768
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
757
769
|
await this.diagramBreadcrumb.click({ timeout: 10000 });
|
|
758
770
|
}
|
|
759
771
|
// Opening the breadcrumb menu leaves the pointer on the breadcrumb button,
|
|
@@ -1631,6 +1643,7 @@ class ModelerCreatePage {
|
|
|
1631
1643
|
}
|
|
1632
1644
|
async fillStartEventVariablesForDocHandling(zeebeUrl, zeebeClientId, zeebeClientSecret) {
|
|
1633
1645
|
console.log(`URL: ${zeebeUrl}, ClientId: ${zeebeClientId}, Secret: ${zeebeClientSecret}`);
|
|
1646
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
1634
1647
|
await (0, test_1.expect)(this.connectorsDocHandlingStartEventElement).toBeVisible({
|
|
1635
1648
|
timeout: 60000,
|
|
1636
1649
|
});
|
|
@@ -282,11 +282,27 @@ class ModelerHomePage {
|
|
|
282
282
|
// lands mid-outage used to lose the rest of its budget), and when the backend
|
|
283
283
|
// stays down the wait is spent on the thing the test is actually waiting for.
|
|
284
284
|
async recoverModelerSession() {
|
|
285
|
-
|
|
285
|
+
// Two distinct ways Modeler loses its session, and only one of them shows
|
|
286
|
+
// the identity provider. When POST /api/internal/login 5xx's during the
|
|
287
|
+
// SPA's own sign-in it never logs out at all -- it parks on Modeler's
|
|
288
|
+
// /login route behind a spinner that nothing retries -- so the
|
|
289
|
+
// credentials path below cannot see it. Un-stick that first.
|
|
290
|
+
const unstuck = await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
291
|
+
if (!(await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page)) && !unstuck) {
|
|
286
292
|
this.consecutiveSessionRecoveries = 0;
|
|
287
293
|
return false;
|
|
288
294
|
}
|
|
289
|
-
|
|
295
|
+
// Counted, not just reset: sessionDropError reports this number, and
|
|
296
|
+
// without the increment the counter read 0 forever, so the `> 0` guard at
|
|
297
|
+
// every call site was unreachable and the dropped-session diagnosis it
|
|
298
|
+
// exists to surface never once printed.
|
|
299
|
+
this.consecutiveSessionRecoveries++;
|
|
300
|
+
// The banner alone does not prove the session came back: Modeler renders
|
|
301
|
+
// its whole app shell, banner included, over the spinner on the hung
|
|
302
|
+
// /login route. Requiring the browser to have LEFT that route is what
|
|
303
|
+
// makes "recovered" mean recovered.
|
|
304
|
+
const shellVisible = !(0, sessionRecovery_1.isAtModelerLoginRoute)(this.page) &&
|
|
305
|
+
(await this.becameVisible(this.modelerPageBanner, 120000));
|
|
290
306
|
if (shellVisible) {
|
|
291
307
|
this.consecutiveSessionRecoveries = 0;
|
|
292
308
|
}
|
|
@@ -386,6 +402,10 @@ class ModelerHomePage {
|
|
|
386
402
|
// Reload so the check reflects the PERSISTED server state, not the
|
|
387
403
|
// optimistic client render that can silently revert to "New project".
|
|
388
404
|
await this.page.reload();
|
|
405
|
+
// A reload is also how the browser ends up on Modeler's hung /login, and
|
|
406
|
+
// the committed name can never render there -- which would read as "the
|
|
407
|
+
// rename did not persist" rather than "the page never loaded".
|
|
408
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
389
409
|
await (0, test_1.expect)(committedProjectName.first()).toBeVisible({ timeout: 15000 });
|
|
390
410
|
}).toPass({ timeout: 120000 });
|
|
391
411
|
}
|
|
@@ -413,6 +433,7 @@ class ModelerHomePage {
|
|
|
413
433
|
// resource is one level down, so re-enter the container first.
|
|
414
434
|
async clickProcessDiagram(name) {
|
|
415
435
|
await this.ensureInResourceContainer();
|
|
436
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
416
437
|
const process = this.rows.filter({ hasText: name }).getByTitle(name).first();
|
|
417
438
|
await (0, test_1.expect)(process).toBeVisible({ timeout: 90000 });
|
|
418
439
|
await process.click();
|
|
@@ -426,6 +447,7 @@ class ModelerHomePage {
|
|
|
426
447
|
const dropdown = this.processApplicationPage.createNewDropdown;
|
|
427
448
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
428
449
|
try {
|
|
450
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
429
451
|
await this.dismissOverlays();
|
|
430
452
|
await (0, test_1.expect)(dropdown).toBeVisible({
|
|
431
453
|
timeout: 45000,
|
|
@@ -452,6 +474,7 @@ class ModelerHomePage {
|
|
|
452
474
|
async clickWorkspaceDiagramTypeDropdown() {
|
|
453
475
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
454
476
|
try {
|
|
477
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
455
478
|
await this.dismissOverlays();
|
|
456
479
|
await (0, test_1.expect)(this.diagramTypeDropdown).toBeVisible({
|
|
457
480
|
timeout: 45000,
|
|
@@ -483,7 +506,21 @@ class ModelerHomePage {
|
|
|
483
506
|
if (await this.becameVisible(this.diagramPropertiesPanel, 1000)) {
|
|
484
507
|
return;
|
|
485
508
|
}
|
|
486
|
-
|
|
509
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
510
|
+
const menuOpen = await this.becameVisible(this.bpmnTemplateOption, 5000);
|
|
511
|
+
// Hub disables this item while its own create-file request is in flight
|
|
512
|
+
// (`disabled={creatingType === BPMN}` in CreateActionDs). A request that
|
|
513
|
+
// never answers leaves it disabled for good -- yet still visible, so the
|
|
514
|
+
// probe above passed and the click below then spent its whole 30s on
|
|
515
|
+
// "element is not enabled" on every single attempt until the test timed
|
|
516
|
+
// out. Only a reload clears that React state; the menu is closed by it,
|
|
517
|
+
// which is why clickDiagramTypeDropdown has to reopen it.
|
|
518
|
+
const stuckDisabled = menuOpen &&
|
|
519
|
+
!(await this.bpmnTemplateOption.isEnabled().catch(() => false));
|
|
520
|
+
if (stuckDisabled) {
|
|
521
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
522
|
+
}
|
|
523
|
+
if (!menuOpen || stuckDisabled) {
|
|
487
524
|
await this.clickDiagramTypeDropdown();
|
|
488
525
|
}
|
|
489
526
|
// 30s rather than the old 120s: the option is proven visible above, so a
|
|
@@ -493,9 +530,15 @@ class ModelerHomePage {
|
|
|
493
530
|
}).toPass({ timeout: 240000, intervals: [1000, 2000, 5000] });
|
|
494
531
|
}
|
|
495
532
|
async clickFormOption() {
|
|
533
|
+
// The create menu only exists on a rendered container page. When the
|
|
534
|
+
// navigation that got us here bounced to Modeler's hung /login, this
|
|
535
|
+
// option is not merely late -- it is on a page that never renders -- so
|
|
536
|
+
// recover before spending the click budget looking for it.
|
|
537
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
496
538
|
await this.formTemplateOption.click();
|
|
497
539
|
}
|
|
498
540
|
async enterFormName(name) {
|
|
541
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
499
542
|
await (0, test_1.expect)(this.formNameInput).toBeVisible({ timeout: 120000 });
|
|
500
543
|
await this.formNameInput.click({ timeout: 60000, force: true });
|
|
501
544
|
await this.formNameInput.fill(name, { timeout: 60000 });
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ProcessApplicationPage = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
|
|
6
|
+
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
6
7
|
// Which process application holds the current test's resources, and which
|
|
7
8
|
// cluster its development stage currently points at. Keyed by the Playwright
|
|
8
9
|
// Page because the object that creates the container (ModelerHomePage) and the
|
|
@@ -92,6 +93,12 @@ class ProcessApplicationPage {
|
|
|
92
93
|
return this.folderExplorer.or(this.breadcrumb).first();
|
|
93
94
|
}
|
|
94
95
|
async waitForProcessApplication(timeout = 90000) {
|
|
96
|
+
// Every route into this page is a navigation, and a navigation is exactly
|
|
97
|
+
// when Modeler can bounce the browser onto its own hung /login. Neither
|
|
98
|
+
// the folder explorer nor the breadcrumb can ever render there, so waiting
|
|
99
|
+
// the full timeout out first would only delay the recovery that has to
|
|
100
|
+
// happen anyway. No-ops when the page is where it should be.
|
|
101
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
95
102
|
await (0, test_1.expect)(this.pageReady()).toBeVisible({ timeout });
|
|
96
103
|
}
|
|
97
104
|
async clickCreateProcessApplicationButton() {
|
|
@@ -208,11 +215,16 @@ class ProcessApplicationPage {
|
|
|
208
215
|
await this.page.goto(`${paUrl}/connected-clusters`, {
|
|
209
216
|
waitUntil: 'domcontentloaded',
|
|
210
217
|
});
|
|
218
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
211
219
|
await this.openStageDefinition();
|
|
212
220
|
await this.defineDevelopmentStage(clusterName);
|
|
213
221
|
}).toPass({ timeout: 240000, intervals: [1000, 2000, 5000] });
|
|
214
222
|
container.developmentCluster = clusterName;
|
|
215
223
|
await this.page.goto(returnTo, { waitUntil: 'domcontentloaded' });
|
|
224
|
+
// Hand the caller back a page it can act on: this navigation can bounce to
|
|
225
|
+
// the hung /login just as easily as the one above, and the caller goes
|
|
226
|
+
// straight into a deploy dialog that would never open there.
|
|
227
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
216
228
|
return true;
|
|
217
229
|
}
|
|
218
230
|
// The /connected-clusters deep link opens the Define stages dialog by itself
|
|
@@ -31,3 +31,43 @@ export declare function isAtIdentityProvider(page: Page): boolean;
|
|
|
31
31
|
* needed here.
|
|
32
32
|
*/
|
|
33
33
|
export declare function restoreSessionIfLoggedOut(page: Page): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the browser is parked on Web Modeler's OWN `/login` route, as
|
|
36
|
+
* opposed to the identity provider's login form.
|
|
37
|
+
*
|
|
38
|
+
* Two different failure states that look alike in a screenshot and need
|
|
39
|
+
* opposite recoveries, so they are kept apart deliberately. The identity
|
|
40
|
+
* provider is a separate HOST (`weblogin.`/`login.`) and wants credentials
|
|
41
|
+
* typed into it -- that is {@link restoreSessionIfLoggedOut}. This one is a
|
|
42
|
+
* PATH on the application's own host, and wants a re-navigation.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isAtModelerLoginRoute(page: Page): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Un-sticks Web Modeler when its own `/login` route has hung, and reports
|
|
47
|
+
* whether it had to.
|
|
48
|
+
*
|
|
49
|
+
* Modeler establishes its session by POSTing `/api/internal/login` from
|
|
50
|
+
* `prepareSession()`. When that call 5xx's the promise rejects and nothing
|
|
51
|
+
* catches it: `history.push(returnTo)` never runs, the app never becomes
|
|
52
|
+
* ready, and the SPA sits on `/login?returnUrl=...` rendering its shell over
|
|
53
|
+
* an infinite spinner. There is no client-side retry and no error state, so
|
|
54
|
+
* the page cannot recover on its own -- every Modeler locator the test waits
|
|
55
|
+
* for simply never appears, and the test spends its entire budget on a page
|
|
56
|
+
* that will never render. The 2026-09-22 nightly lost 22 tests across nine
|
|
57
|
+
* specs to one intermittent backend this way, each reported as an unrelated
|
|
58
|
+
* "locator not found".
|
|
59
|
+
*
|
|
60
|
+
* Reloading re-runs `prepareSession()`, so it re-issues the POST; when that
|
|
61
|
+
* one succeeds the SPA pushes the `returnUrl` it kept and the browser lands on
|
|
62
|
+
* the page the caller was navigating to. Retried because the backend fails
|
|
63
|
+
* intermittently rather than continuously -- on that nightly roughly half the
|
|
64
|
+
* calls answered 200, so a few spaced attempts are what get through. The
|
|
65
|
+
* spacing is the `waitForURL` budget itself rather than a sleep: it returns
|
|
66
|
+
* the moment the session is accepted, and otherwise spends the wait backing
|
|
67
|
+
* off.
|
|
68
|
+
*
|
|
69
|
+
* Cheap to call speculatively -- a page that is not on `/login` costs one URL
|
|
70
|
+
* comparison and no DOM access, which is what lets the Modeler page objects
|
|
71
|
+
* guard their navigations with it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function recoverStuckModelerLogin(page: Page, attempts?: number, settleTimeout?: number): Promise<boolean>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.restoreSessionIfLoggedOut = exports.isAtIdentityProvider = exports.trackSignedInUser = void 0;
|
|
3
|
+
exports.recoverStuckModelerLogin = exports.isAtModelerLoginRoute = exports.restoreSessionIfLoggedOut = exports.isAtIdentityProvider = exports.trackSignedInUser = void 0;
|
|
4
4
|
// Only the username is kept -- no password, no cookie, no token. Keyed by the
|
|
5
5
|
// Playwright Page because the object that logs in (LoginPage) and the objects
|
|
6
6
|
// that later have to recover a dropped session (ModelerHomePage) are separate
|
|
@@ -142,3 +142,85 @@ async function restoreSessionIfLoggedOut(page) {
|
|
|
142
142
|
return true;
|
|
143
143
|
}
|
|
144
144
|
exports.restoreSessionIfLoggedOut = restoreSessionIfLoggedOut;
|
|
145
|
+
const LOGIN_ROUTE = /\/login\/?$/;
|
|
146
|
+
/**
|
|
147
|
+
* Whether the browser is parked on Web Modeler's OWN `/login` route, as
|
|
148
|
+
* opposed to the identity provider's login form.
|
|
149
|
+
*
|
|
150
|
+
* Two different failure states that look alike in a screenshot and need
|
|
151
|
+
* opposite recoveries, so they are kept apart deliberately. The identity
|
|
152
|
+
* provider is a separate HOST (`weblogin.`/`login.`) and wants credentials
|
|
153
|
+
* typed into it -- that is {@link restoreSessionIfLoggedOut}. This one is a
|
|
154
|
+
* PATH on the application's own host, and wants a re-navigation.
|
|
155
|
+
*/
|
|
156
|
+
function isAtModelerLoginRoute(page) {
|
|
157
|
+
if (isAtIdentityProvider(page)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
return LOGIN_ROUTE.test(new URL(page.url()).pathname);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.isAtModelerLoginRoute = isAtModelerLoginRoute;
|
|
168
|
+
/**
|
|
169
|
+
* Un-sticks Web Modeler when its own `/login` route has hung, and reports
|
|
170
|
+
* whether it had to.
|
|
171
|
+
*
|
|
172
|
+
* Modeler establishes its session by POSTing `/api/internal/login` from
|
|
173
|
+
* `prepareSession()`. When that call 5xx's the promise rejects and nothing
|
|
174
|
+
* catches it: `history.push(returnTo)` never runs, the app never becomes
|
|
175
|
+
* ready, and the SPA sits on `/login?returnUrl=...` rendering its shell over
|
|
176
|
+
* an infinite spinner. There is no client-side retry and no error state, so
|
|
177
|
+
* the page cannot recover on its own -- every Modeler locator the test waits
|
|
178
|
+
* for simply never appears, and the test spends its entire budget on a page
|
|
179
|
+
* that will never render. The 2026-09-22 nightly lost 22 tests across nine
|
|
180
|
+
* specs to one intermittent backend this way, each reported as an unrelated
|
|
181
|
+
* "locator not found".
|
|
182
|
+
*
|
|
183
|
+
* Reloading re-runs `prepareSession()`, so it re-issues the POST; when that
|
|
184
|
+
* one succeeds the SPA pushes the `returnUrl` it kept and the browser lands on
|
|
185
|
+
* the page the caller was navigating to. Retried because the backend fails
|
|
186
|
+
* intermittently rather than continuously -- on that nightly roughly half the
|
|
187
|
+
* calls answered 200, so a few spaced attempts are what get through. The
|
|
188
|
+
* spacing is the `waitForURL` budget itself rather than a sleep: it returns
|
|
189
|
+
* the moment the session is accepted, and otherwise spends the wait backing
|
|
190
|
+
* off.
|
|
191
|
+
*
|
|
192
|
+
* Cheap to call speculatively -- a page that is not on `/login` costs one URL
|
|
193
|
+
* comparison and no DOM access, which is what lets the Modeler page objects
|
|
194
|
+
* guard their navigations with it.
|
|
195
|
+
*/
|
|
196
|
+
async function recoverStuckModelerLogin(page, attempts = 4, settleTimeout = 25000) {
|
|
197
|
+
if (!isAtModelerLoginRoute(page)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
201
|
+
console.warn(`recoverStuckModelerLogin: Modeler is stuck on ${page.url()} -- its ` +
|
|
202
|
+
`POST /api/internal/login is failing. Reload ${attempt}/${attempts}.`);
|
|
203
|
+
await page
|
|
204
|
+
.reload({ waitUntil: 'domcontentloaded', timeout: 60000 })
|
|
205
|
+
.catch(() => undefined);
|
|
206
|
+
// The reload can land on the identity provider instead -- Modeler calls
|
|
207
|
+
// Auth0 logout on some of these failures -- and there credentials, not
|
|
208
|
+
// another reload, are what gets us back.
|
|
209
|
+
await restoreSessionIfLoggedOut(page);
|
|
210
|
+
// Leaving /login is the signal that the session was accepted: the SPA only
|
|
211
|
+
// pushes the returnUrl once prepareSession() resolved.
|
|
212
|
+
const recovered = await page
|
|
213
|
+
.waitForURL((url) => !LOGIN_ROUTE.test(url.pathname), {
|
|
214
|
+
timeout: settleTimeout,
|
|
215
|
+
})
|
|
216
|
+
.then(() => true)
|
|
217
|
+
.catch(() => false);
|
|
218
|
+
if (recovered) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
console.warn('recoverStuckModelerLogin: Modeler never left /login -- its backend is ' +
|
|
223
|
+
'still failing the session call.');
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
exports.recoverStuckModelerLogin = recoverStuckModelerLogin;
|