@camunda/e2e-test-suite 0.0.927 → 0.0.929
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.
|
@@ -21,6 +21,21 @@ declare class AppsPage {
|
|
|
21
21
|
constructor(page: Page);
|
|
22
22
|
clickCluster(component: Locator, name: string): Promise<void>;
|
|
23
23
|
clickModeler(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Waits for the Modeler app to actually render, and recovers the one landing
|
|
26
|
+
* state a hostname check cannot see: Modeler's OAuth `/login-callback`
|
|
27
|
+
* intermittently answers 503 on SaaS INT, which parks the browser on that
|
|
28
|
+
* URL with an empty document. The hostname is already `modeler.*`, so
|
|
29
|
+
* nothing downstream notices and the caller's banner assertion just waits
|
|
30
|
+
* out its whole timeout on a dead page. Reloading cannot help either — the
|
|
31
|
+
* authorization code in the callback URL is single-use — so re-enter at the
|
|
32
|
+
* Modeler origin root, which restarts the OAuth flow with a fresh code.
|
|
33
|
+
*
|
|
34
|
+
* Rendering is best-effort: some Modeler renders never expose the banner's
|
|
35
|
+
* accessible name when asset requests get 429-throttled, so a non-rendered
|
|
36
|
+
* result is left for the caller to assert on rather than thrown here.
|
|
37
|
+
*/
|
|
38
|
+
private waitForModelerToRender;
|
|
24
39
|
clickTasklist(clusterName: string): Promise<void>;
|
|
25
40
|
clickIdentity(clusterName: string): Promise<void>;
|
|
26
41
|
private doClickClusterInOperate;
|
|
@@ -69,10 +69,6 @@ class AppsPage {
|
|
|
69
69
|
}
|
|
70
70
|
async clickModeler() {
|
|
71
71
|
const maxRetries = 5;
|
|
72
|
-
const modelerBanner = this.page.getByRole('banner', {
|
|
73
|
-
name: 'Camunda Modeler',
|
|
74
|
-
});
|
|
75
|
-
const modelerDiagramDropdown = this.page.locator('[data-test="diagram-dropdown"]');
|
|
76
72
|
// If the click is already done and we are sitting on Modeler, return
|
|
77
73
|
// immediately — observed in failing runs where the banner role lookup
|
|
78
74
|
// never resolves because partial app.js 429s prevent the accessible
|
|
@@ -92,13 +88,10 @@ class AppsPage {
|
|
|
92
88
|
await this.page.waitForURL((url) => url.hostname.includes('modeler.'), {
|
|
93
89
|
timeout: 90000,
|
|
94
90
|
});
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
await
|
|
99
|
-
modelerBanner.waitFor({ state: 'visible', timeout: 30000 }),
|
|
100
|
-
modelerDiagramDropdown.waitFor({ state: 'visible', timeout: 30000 }),
|
|
101
|
-
]).catch(() => { });
|
|
91
|
+
// Also give the Modeler app a chance to actually render, recovering
|
|
92
|
+
// the one landing state the URL check above cannot see (see
|
|
93
|
+
// waitForModelerToRender).
|
|
94
|
+
await this.waitForModelerToRender();
|
|
102
95
|
return;
|
|
103
96
|
}
|
|
104
97
|
catch (error) {
|
|
@@ -119,6 +112,41 @@ class AppsPage {
|
|
|
119
112
|
}
|
|
120
113
|
throw new Error(`Failed to click the modeler link after ${maxRetries} attempts.`);
|
|
121
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Waits for the Modeler app to actually render, and recovers the one landing
|
|
117
|
+
* state a hostname check cannot see: Modeler's OAuth `/login-callback`
|
|
118
|
+
* intermittently answers 503 on SaaS INT, which parks the browser on that
|
|
119
|
+
* URL with an empty document. The hostname is already `modeler.*`, so
|
|
120
|
+
* nothing downstream notices and the caller's banner assertion just waits
|
|
121
|
+
* out its whole timeout on a dead page. Reloading cannot help either — the
|
|
122
|
+
* authorization code in the callback URL is single-use — so re-enter at the
|
|
123
|
+
* Modeler origin root, which restarts the OAuth flow with a fresh code.
|
|
124
|
+
*
|
|
125
|
+
* Rendering is best-effort: some Modeler renders never expose the banner's
|
|
126
|
+
* accessible name when asset requests get 429-throttled, so a non-rendered
|
|
127
|
+
* result is left for the caller to assert on rather than thrown here.
|
|
128
|
+
*/
|
|
129
|
+
async waitForModelerToRender(maxAttempts = 2) {
|
|
130
|
+
const modelerBanner = this.page.getByRole('banner', {
|
|
131
|
+
name: 'Camunda Modeler',
|
|
132
|
+
});
|
|
133
|
+
const modelerDiagramDropdown = this.page.locator('[data-test="diagram-dropdown"]');
|
|
134
|
+
const modelerRoot = new URL('/', this.page.url()).toString();
|
|
135
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
136
|
+
const rendered = await Promise.race([
|
|
137
|
+
modelerBanner.waitFor({ state: 'visible', timeout: 30000 }),
|
|
138
|
+
modelerDiagramDropdown.waitFor({ state: 'visible', timeout: 30000 }),
|
|
139
|
+
])
|
|
140
|
+
.then(() => true)
|
|
141
|
+
.catch(() => false);
|
|
142
|
+
if (rendered || attempt === maxAttempts - 1) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.warn(`Modeler did not render after landing on ${this.page.url()}; ` +
|
|
146
|
+
`re-entering at ${modelerRoot}`);
|
|
147
|
+
await this.page.goto(modelerRoot, { waitUntil: 'domcontentloaded' });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
122
150
|
async clickTasklist(clusterName) {
|
|
123
151
|
const maxRetries = 5;
|
|
124
152
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
@@ -96,16 +96,16 @@ class ClusterSecretsPage {
|
|
|
96
96
|
console.error('Error during Connector Secrets deletion: ', error);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
// "Create new secret" is absent whenever the panel rendered its "Oops ...
|
|
100
|
+
// something went wrong" state, so a single re-entry of the tab is not enough
|
|
101
|
+
// when the Console API stays unhealthy for a couple of minutes (run
|
|
102
|
+
// 31453329946: the GET .../connectorSecrets never resolved and the button
|
|
103
|
+
// was still missing after re-entering the tab). Heal through
|
|
104
|
+
// assertSecretsToolbarVisible, which keeps re-entering until the toolbar is
|
|
105
|
+
// genuinely rendered, and only then click.
|
|
99
106
|
async clickCreateNewSecretButton(cluster) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
await this.clickClusterBanner();
|
|
105
|
-
await this.clickCluster(cluster);
|
|
106
|
-
await this.clusterSecretTab.click({ timeout: 30000 });
|
|
107
|
-
await this.createNewSecretButton.click({ timeout: 30000 });
|
|
108
|
-
}
|
|
107
|
+
await this.assertSecretsToolbarVisible(cluster);
|
|
108
|
+
await this.createNewSecretButton.click({ timeout: 60000 });
|
|
109
109
|
}
|
|
110
110
|
// A rejected Console API call leaves the create/import modal open with an
|
|
111
111
|
// inline "Error fetch error" banner instead of closing it. Carbon's modal
|
|
@@ -245,7 +245,7 @@ class ClusterSecretsPage {
|
|
|
245
245
|
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.createNewSecretButton, {
|
|
246
246
|
visibilityTimeout: 60000,
|
|
247
247
|
totalTimeout: 300000,
|
|
248
|
-
maxRetries:
|
|
248
|
+
maxRetries: 5,
|
|
249
249
|
postAction: async () => {
|
|
250
250
|
await this.reopenConnectorSecretsTab(cluster);
|
|
251
251
|
},
|
|
@@ -274,8 +274,8 @@ class ClusterSecretsPage {
|
|
|
274
274
|
// re-entering the tab brings it back.
|
|
275
275
|
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.importButton, {
|
|
276
276
|
visibilityTimeout: 30000,
|
|
277
|
-
totalTimeout:
|
|
278
|
-
maxRetries:
|
|
277
|
+
totalTimeout: 300000,
|
|
278
|
+
maxRetries: 5,
|
|
279
279
|
postAction: async () => {
|
|
280
280
|
await this.reopenConnectorSecretsTab(cluster);
|
|
281
281
|
},
|
|
@@ -323,8 +323,15 @@ class ClusterSecretsPage {
|
|
|
323
323
|
// (observed in run 30971320305 for both cluster setups), so keep re-entering
|
|
324
324
|
// the tab until the toolbar is genuinely rendered.
|
|
325
325
|
await this.assertSecretsToolbarVisible(cluster);
|
|
326
|
+
// The refresh below is what makes the just-imported secrets show up in the
|
|
327
|
+
// table assertSecretsOrCreate searches, but it also re-issues the same
|
|
328
|
+
// .../connectorSecrets request that breaks the panel - in run 31453329946
|
|
329
|
+
// it did exactly that and left assertSecretsOrCreate reading an
|
|
330
|
+
// "Oops ... something went wrong" panel. Re-assert the toolbar after the
|
|
331
|
+
// reload instead of sleeping blindly, so the panel is proven usable (and
|
|
332
|
+
// healed if not) before the caller reads it.
|
|
326
333
|
await this.page.reload();
|
|
327
|
-
await
|
|
334
|
+
await this.assertSecretsToolbarVisible(cluster);
|
|
328
335
|
}
|
|
329
336
|
}
|
|
330
337
|
exports.ClusterSecretsPage = ClusterSecretsPage;
|
|
@@ -42,6 +42,7 @@ declare class ModelerHomePage {
|
|
|
42
42
|
enterFormName(name: string): Promise<void>;
|
|
43
43
|
clickProjectBreadcrumb(): Promise<void>;
|
|
44
44
|
clickHomeBreadcrumb(): Promise<void>;
|
|
45
|
+
private dismissBlockingDialogs;
|
|
45
46
|
clickOpenOrganizationsButton(): Promise<void>;
|
|
46
47
|
createForm(formName: string): Promise<void>;
|
|
47
48
|
clickCreateIdpApplicationButton(): Promise<void>;
|
|
@@ -198,11 +198,41 @@ class ModelerHomePage {
|
|
|
198
198
|
await this.projectBreadcrumb.click({ timeout: 60000 });
|
|
199
199
|
}
|
|
200
200
|
async clickHomeBreadcrumb() {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
// The Console shell's HelpCenter panel ("Your recommendations") renders on
|
|
202
|
+
// top of Web Modeler as a modal dialog that covers the whole page, so the
|
|
203
|
+
// breadcrumb click retries against an element whose pointer events the
|
|
204
|
+
// overlay swallows until the action timeout expires. Dismiss any such
|
|
205
|
+
// overlay before every attempt instead of clicking blindly.
|
|
206
|
+
const homeLink = this.homeBreadcrumb
|
|
207
|
+
.or(this.page.getByText('Home', { exact: true }))
|
|
208
|
+
.first();
|
|
209
|
+
await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, homeLink, {
|
|
210
|
+
visibilityTimeout: 15000,
|
|
211
|
+
totalTimeout: 120000,
|
|
212
|
+
maxRetries: 4,
|
|
213
|
+
preAction: async () => {
|
|
214
|
+
await this.dismissBlockingDialogs();
|
|
215
|
+
},
|
|
216
|
+
postAction: async () => {
|
|
217
|
+
await this.dismissBlockingDialogs();
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async dismissBlockingDialogs() {
|
|
222
|
+
for (const dialog of await this.page.getByRole('dialog').all()) {
|
|
223
|
+
if (!(await dialog.isVisible().catch(() => false))) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
await this.page.keyboard.press('Escape');
|
|
227
|
+
await dialog.waitFor({ state: 'hidden', timeout: 3000 }).catch(() => { });
|
|
228
|
+
if (!(await dialog.isVisible().catch(() => false))) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const closeButton = dialog.getByRole('button', { name: 'Close' }).first();
|
|
232
|
+
await closeButton
|
|
233
|
+
.waitFor({ state: 'visible', timeout: 3000 })
|
|
234
|
+
.then(() => closeButton.click({ timeout: 5000 }))
|
|
235
|
+
.catch(() => { });
|
|
206
236
|
}
|
|
207
237
|
}
|
|
208
238
|
async clickOpenOrganizationsButton() {
|