@camunda/e2e-test-suite 0.0.633 → 0.0.634
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.7/ClusterSecretsPage.js +23 -7
- package/dist/pages/8.7/ConsoleOrganizationPage.js +10 -1
- package/dist/pages/8.7/HomePage.js +13 -9
- package/dist/pages/8.7/LoginPage.js +18 -5
- package/dist/pages/8.7/PlayPage.d.ts +5 -1
- package/dist/pages/8.7/PlayPage.js +77 -16
- package/dist/pages/8.7/SignUpPage.js +28 -4
- package/dist/pages/8.7/TaskDetailsPage.js +14 -3
- package/dist/pages/8.7/TaskPanelPage.js +5 -4
- package/dist/pages/8.7/UtilitiesPage.js +39 -6
- package/dist/tests/8.7/hto-user-flows.spec.js +1 -1
- package/dist/tests/8.7/web-modeler-user-flows.spec.js +14 -3
- package/dist/utils/mailSlurpClient.js +9 -10
- package/package.json +1 -1
|
@@ -63,21 +63,37 @@ class ClusterSecretsPage {
|
|
|
63
63
|
return; //No Connector Secrets found in the list
|
|
64
64
|
}
|
|
65
65
|
try {
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
for (let sweep = 0; sweep < 20; sweep++) {
|
|
67
|
+
const options = await this.optionsButton.all();
|
|
68
|
+
if (options.length === 0) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
let deletedAny = false;
|
|
72
|
+
for (const optionButton of options) {
|
|
73
|
+
if (!(await optionButton.isVisible().catch(() => false))) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
70
76
|
await optionButton.click({ timeout: 60000 });
|
|
77
|
+
await (0, test_1.expect)(this.deleteConnectorSecretButton).toBeVisible({
|
|
78
|
+
timeout: 15000,
|
|
79
|
+
});
|
|
80
|
+
if (await this.deleteConnectorSecretButton.isDisabled()) {
|
|
81
|
+
await this.page.keyboard.press('Escape').catch(() => { });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
71
84
|
await this.deleteConnectorSecretButton.click({ timeout: 60000 });
|
|
72
85
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 40000 });
|
|
73
86
|
await this.deleteConnectorSecretSubButton.click();
|
|
74
87
|
await (0, test_1.expect)(this.page.getByText('Deleting...')).not.toBeVisible({
|
|
75
88
|
timeout: 60000,
|
|
76
89
|
});
|
|
90
|
+
deletedAny = true;
|
|
91
|
+
await (0, sleep_1.sleep)(3000);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (!deletedAny) {
|
|
95
|
+
break;
|
|
77
96
|
}
|
|
78
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
79
|
-
await sleep(3000);
|
|
80
|
-
options = await this.optionsButton.all();
|
|
81
97
|
}
|
|
82
98
|
await (0, test_1.expect)(this.optionsButton).not.toBeVisible({
|
|
83
99
|
timeout: 30000,
|
|
@@ -194,7 +194,16 @@ class ConsoleOrganizationPage {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
async clickUsersTab() {
|
|
197
|
-
|
|
197
|
+
// The Users tab can fail to render if the Org page is mid-load when we
|
|
198
|
+
// arrive. Try once with a shorter window, reload on failure, then give
|
|
199
|
+
// the second attempt the full timeout before surfacing the error.
|
|
200
|
+
try {
|
|
201
|
+
await (0, test_1.expect)(this.usersTab).toBeVisible({ timeout: 30000 });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
await this.page.reload();
|
|
205
|
+
await (0, test_1.expect)(this.usersTab).toBeVisible({ timeout: 60000 });
|
|
206
|
+
}
|
|
198
207
|
await this.usersTab.click({ timeout: 60000 });
|
|
199
208
|
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.filterTableSearchbox, {
|
|
200
209
|
postAction: async () => {
|
|
@@ -74,7 +74,13 @@ class HomePage {
|
|
|
74
74
|
if (!(await next.isVisible({ timeout: visibleTimeout }))) {
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
try {
|
|
78
|
+
await next.click({ timeout: 30000, force: true });
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Close buttons are occasionally replaced during modal animation;
|
|
82
|
+
// continue and retry with the latest visible control.
|
|
83
|
+
}
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
organizationUuid() {
|
|
@@ -89,15 +95,13 @@ class HomePage {
|
|
|
89
95
|
await this.openOrganizationButton.click({ timeout: 60000 });
|
|
90
96
|
}
|
|
91
97
|
async clickSkipCustomization() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
console.error(error);
|
|
98
|
+
const hasCustomizationModal = await this.buttonSkipCustomization
|
|
99
|
+
.isVisible({ timeout: 30000 })
|
|
100
|
+
.catch(() => false);
|
|
101
|
+
if (hasCustomizationModal) {
|
|
102
|
+
await this.buttonSkipCustomization.click({ timeout: 60000, force: true });
|
|
100
103
|
}
|
|
104
|
+
await this.closeInformationDialog();
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
exports.HomePage = HomePage;
|
|
@@ -19,8 +19,11 @@ class LoginPage {
|
|
|
19
19
|
// resolves to the auth0 email input, regardless of redirect timing.
|
|
20
20
|
this.usernameInput = page
|
|
21
21
|
.getByLabel('Email address')
|
|
22
|
-
.and(page.locator(':not(
|
|
23
|
-
this.passwordInput = page
|
|
22
|
+
.and(page.locator(':not([id="c4-invite-email"])'));
|
|
23
|
+
this.passwordInput = page
|
|
24
|
+
.getByLabel(/^Password\s*\*?$/i)
|
|
25
|
+
.or(page.locator('input[type="password"]'))
|
|
26
|
+
.first();
|
|
24
27
|
this.continueButton = page.getByRole('button', {
|
|
25
28
|
name: 'Continue',
|
|
26
29
|
exact: true,
|
|
@@ -62,6 +65,9 @@ class LoginPage {
|
|
|
62
65
|
const { username = process.env.C8_USERNAME_TEST, password = process.env.C8_PASSWORD_TEST, } = credentials;
|
|
63
66
|
// Navigate to app root to start the auth flow.
|
|
64
67
|
await this.page.goto('/');
|
|
68
|
+
await this.page
|
|
69
|
+
.waitForURL((url) => !url.hostname.includes('console'), { timeout: 60000 })
|
|
70
|
+
.catch(() => { });
|
|
65
71
|
// SSO re-auth may silently log us back in as the previous user after
|
|
66
72
|
// logout/session-reset, keeping the page on Console instead of redirecting
|
|
67
73
|
// to the Auth0 login form. Detect Console (settings button visible) and
|
|
@@ -81,9 +87,16 @@ class LoginPage {
|
|
|
81
87
|
await this.page.waitForLoadState('domcontentloaded', { timeout: 30000 });
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
const onPasswordStep = (await this.passwordHeading
|
|
91
|
+
.isVisible({ timeout: 5000 })
|
|
92
|
+
.catch(() => false)) ||
|
|
93
|
+
(await this.passwordInput.isVisible({ timeout: 5000 }).catch(() => false));
|
|
94
|
+
if (!onPasswordStep) {
|
|
95
|
+
await (0, test_1.expect)(this.usernameInput).toBeVisible({ timeout: 120000 });
|
|
96
|
+
await this.fillUsername(username);
|
|
97
|
+
await this.clickContinueButton();
|
|
98
|
+
}
|
|
99
|
+
await (0, test_1.expect)(this.passwordInput).toBeVisible({ timeout: 180000 });
|
|
87
100
|
await this.fillPassword(password);
|
|
88
101
|
await (0, test_1.expect)(this.loginButton).toBeVisible({ timeout: 120000 });
|
|
89
102
|
await this.clickLoginButton();
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
2
|
declare class PlayPage {
|
|
3
3
|
private page;
|
|
4
|
+
readonly diagram: Locator;
|
|
5
|
+
readonly playTab: Locator;
|
|
4
6
|
readonly completeJobButton: Locator;
|
|
5
|
-
readonly
|
|
7
|
+
readonly configureTestPanel: Locator;
|
|
8
|
+
readonly configureTestPanelStartButton: Locator;
|
|
9
|
+
readonly startInstanceOverlayButton: Locator;
|
|
6
10
|
constructor(page: Page);
|
|
7
11
|
waitForCompleteJobButtonToBeAvailable(): Promise<void>;
|
|
8
12
|
clickCompleteJobButton(): Promise<void>;
|
|
@@ -2,19 +2,35 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PlayPage = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
|
+
const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
|
|
5
6
|
const maxWaitTimeSeconds = 180000;
|
|
6
7
|
class PlayPage {
|
|
7
8
|
page;
|
|
9
|
+
diagram;
|
|
10
|
+
playTab;
|
|
8
11
|
completeJobButton;
|
|
9
|
-
|
|
12
|
+
configureTestPanel;
|
|
13
|
+
configureTestPanelStartButton;
|
|
14
|
+
startInstanceOverlayButton;
|
|
10
15
|
constructor(page) {
|
|
11
16
|
this.page = page;
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
this.diagram = page.getByTestId('diagram');
|
|
18
|
+
this.playTab = page.getByRole('tab', { name: 'Play' });
|
|
19
|
+
this.completeJobButton = this.diagram.getByLabel('Complete job');
|
|
20
|
+
// Newer Modeler Play UI: the "Configure Scenario" floating card hosts a
|
|
21
|
+
// primary "Start" Carbon button (variants: "Start", "Start with
|
|
22
|
+
// variables", "Start with a form"). Source:
|
|
23
|
+
// camunda-hub frontend/packages/modeler/play/src/Definition/
|
|
24
|
+
// ConfigureTestPanel/index.tsx — renders <FloatingCard
|
|
25
|
+
// data-testid="configure-test-panel"> with a <Button> whose visible
|
|
26
|
+
// text starts with "Start".
|
|
27
|
+
this.configureTestPanel = page.getByTestId('configure-test-panel');
|
|
28
|
+
this.configureTestPanelStartButton = this.configureTestPanel.getByRole('button', { name: /^Start( with .*)?$/, exact: false });
|
|
29
|
+
// Legacy bpmn-js canvas overlay button (still emitted by older Modeler
|
|
30
|
+
// builds, also relabeled with cached/example-data suffixes). Kept as a
|
|
31
|
+
// fallback for environments that haven't shipped the ConfigureTestPanel
|
|
32
|
+
// yet.
|
|
33
|
+
this.startInstanceOverlayButton = this.diagram.getByLabel('Start instance', { exact: false });
|
|
18
34
|
}
|
|
19
35
|
async waitForCompleteJobButtonToBeAvailable() {
|
|
20
36
|
await (0, test_1.expect)(this.completeJobButton).toBeVisible({
|
|
@@ -28,17 +44,62 @@ class PlayPage {
|
|
|
28
44
|
await this.completeJobButton.click();
|
|
29
45
|
}
|
|
30
46
|
async clickStartInstanceButton() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
// The Modeler Play UI has two surfaces that can hold the start trigger:
|
|
48
|
+
// - new: a "Start" Carbon button inside the ConfigureTestPanel
|
|
49
|
+
// floating card (`[data-testid="configure-test-panel"]`). This is
|
|
50
|
+
// what newer Modeler builds render. Variants: "Start",
|
|
51
|
+
// "Start with variables", "Start with a form".
|
|
52
|
+
// - legacy: a bpmn-js canvas overlay labelled "Start instance"
|
|
53
|
+
// (sometimes "Start instance with cached data" once a scenario
|
|
54
|
+
// chip exists). Older Modeler builds emit this; newer builds do
|
|
55
|
+
// not, so it cannot be the only locator.
|
|
56
|
+
//
|
|
57
|
+
// Try whichever appears first. On total failure, reload AND re-open
|
|
58
|
+
// the Play tab (the legacy code path called `page.reload()` alone,
|
|
59
|
+
// which dumped the user back on the Implement tab on stable/8.7).
|
|
60
|
+
const startTrigger = this.configureTestPanelStartButton
|
|
61
|
+
.or(this.startInstanceOverlayButton)
|
|
62
|
+
.first();
|
|
63
|
+
const tryClick = async () => {
|
|
64
|
+
await (0, test_1.expect)(this.diagram).toBeVisible({ timeout: 120000 });
|
|
65
|
+
await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, startTrigger, {
|
|
66
|
+
totalTimeout: 240000,
|
|
67
|
+
visibilityTimeout: 60000,
|
|
68
|
+
maxRetries: 8,
|
|
69
|
+
retryDelayMs: 5000,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
await tryClick();
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.warn(`clickStartInstanceButton: first pass failed (${error}); reloading, re-opening Play tab, and retrying once`);
|
|
77
|
+
await this.page.reload();
|
|
78
|
+
// After reload, the Modeler defaults to the Implement tab. Move
|
|
79
|
+
// back to Play before trying again — otherwise the diagram
|
|
80
|
+
// visibility check times out against the Implement-tab DOM.
|
|
81
|
+
if (await this.playTab.isVisible({ timeout: 30000 }).catch(() => false)) {
|
|
82
|
+
await this.playTab.click();
|
|
83
|
+
}
|
|
84
|
+
await tryClick();
|
|
85
|
+
}
|
|
35
86
|
}
|
|
36
87
|
async dismissStartModal() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
88
|
+
// The intro dialog cycles labels across Modeler versions/states.
|
|
89
|
+
// Try the known variations in order; first match wins.
|
|
90
|
+
const buttonVariations = [
|
|
91
|
+
'Start a process instance',
|
|
92
|
+
'Start another instance',
|
|
93
|
+
'Start new instance',
|
|
94
|
+
'Start instance',
|
|
95
|
+
];
|
|
96
|
+
for (const buttonName of buttonVariations) {
|
|
97
|
+
const button = this.page.getByRole('button', { name: buttonName });
|
|
98
|
+
if ((await button.count()) > 0) {
|
|
99
|
+
await button.first().click();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
42
103
|
}
|
|
43
104
|
async waitForInstanceDetailsToBeLoaded() {
|
|
44
105
|
const maxRetries = 2;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SignUpPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
4
5
|
class SignUpPage {
|
|
5
6
|
page;
|
|
6
7
|
firstNameInput;
|
|
@@ -90,10 +91,33 @@ class SignUpPage {
|
|
|
90
91
|
await this.clickPasswordInput();
|
|
91
92
|
await this.fillPasswordInput(password);
|
|
92
93
|
await this.clickSignupButton();
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// Sign-up can complete through multiple UI states across Auth0 variants.
|
|
95
|
+
await test_1.expect
|
|
96
|
+
.poll(async () => {
|
|
97
|
+
const pathname = new URL(this.page.url()).pathname;
|
|
98
|
+
if (pathname !== '/signup') {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
const canSeeVerificationText = await this.verifyEmailText
|
|
102
|
+
.isVisible({ timeout: 1000 })
|
|
103
|
+
.catch(() => false);
|
|
104
|
+
if (canSeeVerificationText) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const canSeeLoginButton = await this.loginToCamundaButton
|
|
108
|
+
.isVisible({ timeout: 1000 })
|
|
109
|
+
.catch(() => false);
|
|
110
|
+
if (canSeeLoginButton) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
const signUpButtonHidden = await this.signupButton
|
|
114
|
+
.isHidden({ timeout: 1000 })
|
|
115
|
+
.catch(() => false);
|
|
116
|
+
return signUpButtonHidden;
|
|
117
|
+
}, {
|
|
118
|
+
timeout: 240000,
|
|
119
|
+
})
|
|
120
|
+
.toBe(true);
|
|
97
121
|
}
|
|
98
122
|
async clickFirstNameInput() {
|
|
99
123
|
await this.firstNameInput.click({ timeout: 30000 });
|
|
@@ -229,9 +229,20 @@ class TaskDetailsPage {
|
|
|
229
229
|
const lastPdfViewer = containers
|
|
230
230
|
.last()
|
|
231
231
|
.locator('.fjs-documentPreview-pdf-viewer');
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
// The PDF viewer can take longer to mount on AWS clusters; if the first
|
|
233
|
+
// wait misses, reload once and give the second wait more headroom before
|
|
234
|
+
// failing the test.
|
|
235
|
+
try {
|
|
236
|
+
await (0, test_1.expect)(firstPdfViewer.or(lastPdfViewer)).toBeVisible({
|
|
237
|
+
timeout: 60000,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
await this.page.reload();
|
|
242
|
+
await (0, test_1.expect)(firstPdfViewer.or(lastPdfViewer)).toBeVisible({
|
|
243
|
+
timeout: 90000,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
235
246
|
}
|
|
236
247
|
async clickSubmitButton() {
|
|
237
248
|
await (0, test_1.expect)(this.submitButton).toBeVisible();
|
|
@@ -23,12 +23,13 @@ class TaskPanelPage {
|
|
|
23
23
|
async openTask(name) {
|
|
24
24
|
let attempts = 0;
|
|
25
25
|
const maxAttempts = 3;
|
|
26
|
+
const taskLocator = this.availableTasks
|
|
27
|
+
.getByText(name, { exact: true })
|
|
28
|
+
.first();
|
|
26
29
|
while (attempts < maxAttempts) {
|
|
27
30
|
try {
|
|
28
|
-
await
|
|
29
|
-
|
|
30
|
-
.nth(0)
|
|
31
|
-
.click({ timeout: 120000 });
|
|
31
|
+
await (0, test_1.expect)(taskLocator).toBeVisible({ timeout: 120000 });
|
|
32
|
+
await taskLocator.click({ timeout: 180000 });
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
catch (error) {
|
|
@@ -5,13 +5,37 @@ const test_1 = require("@playwright/test");
|
|
|
5
5
|
const ModelerHomePage_1 = require("./ModelerHomePage");
|
|
6
6
|
const HomePage_1 = require("./HomePage");
|
|
7
7
|
const sleep_1 = require("../../utils/sleep");
|
|
8
|
-
const randomSleep_1 = require("../../utils/randomSleep");
|
|
9
8
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
10
9
|
const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
|
|
11
|
-
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries =
|
|
10
|
+
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5, skipOrgAssertion = false) {
|
|
11
|
+
// Pre-flight worker stagger: spread the initial Auth0 hits across parallel
|
|
12
|
+
// workers BEFORE the first goto, otherwise all workers slam the tenant at
|
|
13
|
+
// the same instant and several get throttled into the retry loop.
|
|
14
|
+
await (0, sleep_1.sleep)(timeout);
|
|
12
15
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
13
16
|
try {
|
|
14
|
-
await page.
|
|
17
|
+
await page.context().clearCookies();
|
|
18
|
+
await page.goto('about:blank');
|
|
19
|
+
await page
|
|
20
|
+
.evaluate(() => {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.clear();
|
|
23
|
+
}
|
|
24
|
+
catch (_) {
|
|
25
|
+
// Storage can be unavailable in some contexts.
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
sessionStorage.clear();
|
|
29
|
+
}
|
|
30
|
+
catch (_) {
|
|
31
|
+
// Storage can be unavailable in some contexts.
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.catch(() => { });
|
|
35
|
+
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
36
|
+
await page
|
|
37
|
+
.waitForLoadState('networkidle', { timeout: 30000 })
|
|
38
|
+
.catch(() => { });
|
|
15
39
|
await (0, sleep_1.sleep)(timeout);
|
|
16
40
|
// Always detect SSO re-auth: after a logout + clearCookies, Auth0 may
|
|
17
41
|
// silently log the previous user back in without showing the login form.
|
|
@@ -23,7 +47,10 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
|
|
|
23
47
|
.waitFor({ state: 'visible', timeout: 15000 })
|
|
24
48
|
.then(() => true)
|
|
25
49
|
.catch(() => false);
|
|
26
|
-
|
|
50
|
+
const onPasswordStep = await loginPage.passwordHeading
|
|
51
|
+
.isVisible({ timeout: 3000 })
|
|
52
|
+
.catch(() => false);
|
|
53
|
+
if (onAuth0 || onPasswordStep)
|
|
27
54
|
break;
|
|
28
55
|
if (await settingsBtn.isVisible({ timeout: 3000 })) {
|
|
29
56
|
await settingsBtn.click({ timeout: 30000 });
|
|
@@ -31,6 +58,7 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
|
|
|
31
58
|
await page.waitForLoadState('domcontentloaded', { timeout: 30000 });
|
|
32
59
|
}
|
|
33
60
|
}
|
|
61
|
+
await (0, test_1.expect)(loginPage.loginMessage.or(loginPage.passwordHeading)).toBeVisible({ timeout: 60000 });
|
|
34
62
|
if (skipOrgAssertion) {
|
|
35
63
|
await loginPage.loginWithoutOrgAssertion(testUser);
|
|
36
64
|
}
|
|
@@ -41,8 +69,13 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
|
|
|
41
69
|
}
|
|
42
70
|
catch (error) {
|
|
43
71
|
if (attempt < maxRetries - 1) {
|
|
44
|
-
|
|
45
|
-
|
|
72
|
+
// Exponential backoff with jitter: 10s, 20s, 40s, 80s (capped at 80s).
|
|
73
|
+
// Auth0 throttling under heavy parallel load needs longer recovery
|
|
74
|
+
// than a flat 10-20s — give the tenant time to drain.
|
|
75
|
+
const base = Math.min(10000 * 2 ** attempt, 80000);
|
|
76
|
+
const backoff = base + Math.floor(Math.random() * 10000);
|
|
77
|
+
console.warn(`Attempt ${attempt + 1} failed for logging in. Retrying in ${backoff}ms...`);
|
|
78
|
+
await (0, sleep_1.sleep)(backoff);
|
|
46
79
|
}
|
|
47
80
|
else {
|
|
48
81
|
console.error(error);
|
|
@@ -434,7 +434,7 @@ _8_7_1.test.describe('HTO User Flow Tests', () => {
|
|
|
434
434
|
await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, `${taskName}1`, 'low');
|
|
435
435
|
await taskPanelPage.filterBy('Completed');
|
|
436
436
|
await (0, test_1.expect)(page.getByText(processName).first()).toBeVisible({
|
|
437
|
-
timeout:
|
|
437
|
+
timeout: 120000,
|
|
438
438
|
});
|
|
439
439
|
await (0, test_1.expect)(page.getByText(processName)).toHaveCount(4);
|
|
440
440
|
await taskPanelPage.openTask(`${taskName}4`);
|
|
@@ -432,9 +432,20 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
432
432
|
});
|
|
433
433
|
await _8_7_1.test.step('Log in to C8 as New User', async () => {
|
|
434
434
|
await (0, UtilitiesPage_1.clickInvitationLinkInEmail)(page, id);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
// The OAuth redirect chain after the invitation link can stall and
|
|
436
|
+
// never resolve to the Modeler banner within a single wait window.
|
|
437
|
+
// Reload the page once and try again before giving up.
|
|
438
|
+
try {
|
|
439
|
+
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
440
|
+
timeout: 90000,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
await page.reload();
|
|
445
|
+
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
446
|
+
timeout: 180000,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
438
449
|
await modelerHomePage.clickMessageBanner();
|
|
439
450
|
await settingsPage.clickOpenSettingsButton();
|
|
440
451
|
await settingsPage.clickLogoutButton();
|
|
@@ -17,20 +17,19 @@ async function createInbox(emailAddress, expriesIn = 1200000) {
|
|
|
17
17
|
}
|
|
18
18
|
exports.createInbox = createInbox;
|
|
19
19
|
async function deleteInbox(id) {
|
|
20
|
+
if (!id) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
20
23
|
try {
|
|
21
24
|
console.log(`Deleting inbox ${id}`);
|
|
22
|
-
await
|
|
25
|
+
await Promise.race([
|
|
26
|
+
exports.mailSlurp.deleteInbox(id),
|
|
27
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('deleteInbox timeout exceeded')), 20000)),
|
|
28
|
+
]);
|
|
23
29
|
}
|
|
24
30
|
catch (error) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
error.message.includes('does not exist'))) {
|
|
28
|
-
console.log('Failed to create inbox, could be a timing issue on MailSlurp side: ' +
|
|
29
|
-
String(error));
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
throw new Error('Failed to delete inbox: ' + String(error));
|
|
33
|
-
}
|
|
31
|
+
// Cleanup should be best-effort and must not fail the test flow.
|
|
32
|
+
console.warn('Failed to delete inbox: ' + String(error));
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
exports.deleteInbox = deleteInbox;
|