@camunda/e2e-test-suite 0.0.1052 → 0.0.1053

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.
@@ -3,6 +3,7 @@ import { McpCredentials } from '../../utils/mcpSecrets';
3
3
  import { ClientCredentialsDetailsPage } from './ClientCredentialsDetailsPage';
4
4
  declare class ClusterDetailsPage {
5
5
  private page;
6
+ private pendingClientScopeCheck;
6
7
  readonly apiTab: Locator;
7
8
  readonly createFirstClientButton: Locator;
8
9
  readonly clientNameTextbox: Locator;
@@ -62,6 +63,8 @@ declare class ClusterDetailsPage {
62
63
  clickOptimizeCheckbox(): Promise<void>;
63
64
  clickCreateButton(): Promise<void>;
64
65
  clickCloseModalButton(): Promise<void>;
66
+ assertAPIClientScopes(name: string): Promise<void>;
67
+ filterClientsList(name: string): Promise<void>;
65
68
  clickSettingsTab(): Promise<void>;
66
69
  enableAuthorizations(): Promise<void>;
67
70
  disableAuthorizations(): Promise<void>;
@@ -8,6 +8,9 @@ const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLoc
8
8
  const ClientCredentialsDetailsPage_1 = require("./ClientCredentialsDetailsPage");
9
9
  class ClusterDetailsPage {
10
10
  page;
11
+ // Name of a client whose scopes still have to be checked against the
12
+ // clients list — see createAPIClient() / clickCloseModalButton().
13
+ pendingClientScopeCheck = null;
11
14
  apiTab;
12
15
  createFirstClientButton;
13
16
  clientNameTextbox;
@@ -235,6 +238,47 @@ class ClusterDetailsPage {
235
238
  }
236
239
  async clickCloseModalButton() {
237
240
  await this.closeModalButton.click({ timeout: 60000 });
241
+ const pendingClient = this.pendingClientScopeCheck;
242
+ if (pendingClient === null) {
243
+ return;
244
+ }
245
+ this.pendingClientScopeCheck = null;
246
+ await this.assertAPIClientScopes(pendingClient);
247
+ }
248
+ // Asserts the scopes an API client was created with, read from its row in
249
+ // the clients list. Only meaningful once the credentials modal is closed:
250
+ // while it is open the panel behind it can still be showing its pre-create
251
+ // state and lists no row for the new client at all.
252
+ async assertAPIClientScopes(name) {
253
+ const row = this.clientsList.filter({ hasText: name });
254
+ const scopes = /(?=.*Orchestration)(?=.*Optimize)(?=.*Secrets)/;
255
+ await this.filterClientsList(name);
256
+ try {
257
+ await (0, test_1.expect)(row).toContainText(scopes, { timeout: 30000 });
258
+ }
259
+ catch (error) {
260
+ // A panel that rendered before the client existed — or that failed into
261
+ // the "Oops ... something went wrong." state — only picks the new client
262
+ // up on a refetch, which is what reloading the API tab forces.
263
+ console.warn(`assertAPIClientScopes: "${name}" is not listed yet; reloading the API tab and retrying. ${error}`);
264
+ await this.dismissOopsIfPresent();
265
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
266
+ await this.clickAPITab();
267
+ await this.filterClientsList(name);
268
+ await (0, test_1.expect)(row).toContainText(scopes, { timeout: 60000 });
269
+ }
270
+ }
271
+ // Narrows the clients table to one client through the table's own filter.
272
+ // The table paginates at ten rows and is not ordered by creation time, so on
273
+ // a cluster that already has clients a newly created one is usually on a
274
+ // page other than the one being displayed — matching a row without filtering
275
+ // first reports it as missing.
276
+ async filterClientsList(name) {
277
+ const filterInput = this.page.getByRole('searchbox', {
278
+ name: 'Filter table',
279
+ });
280
+ await (0, test_1.expect)(filterInput).toBeVisible({ timeout: 60000 });
281
+ await filterInput.fill(name);
238
282
  }
239
283
  async clickSettingsTab() {
240
284
  await this.settingsTab.click({ timeout: 60000 });
@@ -515,24 +559,16 @@ class ClusterDetailsPage {
515
559
  await (0, test_1.expect)(this.clientCredentialsDialog
516
560
  .getByText('The Client Secret will not be shown again.')
517
561
  .first()).toBeVisible();
518
- try {
519
- await (0, test_1.expect)(this.clientsList.filter({ hasText: attemptName })).toContainText(/(?=.*Orchestration)(?=.*Optimize)(?=.*Secrets)/, {
520
- timeout: 10000,
521
- });
522
- }
523
- catch (err) {
524
- // The clients list panel can land in "Oops ... something went wrong."
525
- // while the post-create modal is still open. The modal blocks pointer
526
- // events so we can't click Reload here; the modal's "Client Secret will
527
- // not be shown again" message above already proves the client exists.
528
- const oopsVisible = await this.page
529
- .getByRole('heading', { name: /Oops/i })
530
- .isVisible({ timeout: 1000 })
531
- .catch(() => false);
532
- if (!oopsVisible)
533
- throw err;
534
- console.warn(`createAPIClient: clients list panel is in Oops state; skipping row scope assertion for "${attemptName}".`);
535
- }
562
+ // The client now exists — the modal above is proof — so the scopes it
563
+ // was created with are checked in clickCloseModalButton() rather than
564
+ // here. The clients list behind the open modal is not a reliable
565
+ // source for that check: it can still be rendering its pre-create
566
+ // state (no row for this client) or have failed into "Oops ...
567
+ // something went wrong.", and the modal blocks the pointer events a
568
+ // reload would need. Asserting it here instead sent a created client
569
+ // round the retry loop, which both left orphan clients behind and
570
+ // discarded the credentials the caller has yet to read.
571
+ this.pendingClientScopeCheck = attemptName;
536
572
  return;
537
573
  }
538
574
  catch (error) {
@@ -152,7 +152,6 @@ class ProcessApplicationPage {
152
152
  // taken -- callers that care assert the name in the deploy dialog anyway.
153
153
  async defineDevelopmentStage(clusterName) {
154
154
  await (0, test_1.expect)(this.defineStagesDialog).toBeVisible({ timeout: 60000 });
155
- await this.developmentClusterSelect.click({ timeout: 30000 });
156
155
  // Escape before interpolating: a cluster name can carry regex
157
156
  // metacharacters -- "Test Cluster (v2)" is the common case -- which would
158
157
  // otherwise mis-match or throw. Same treatment as
@@ -166,7 +165,31 @@ class ProcessApplicationPage {
166
165
  .getByRole('option')
167
166
  .filter({ hasNotText: 'Select a development cluster' })
168
167
  .first();
169
- await option.click({ timeout: 30000 });
168
+ // Committing a selection is what unmounts the option list: the dropdown is
169
+ // a Carbon Dropdown over Downshift, so the click that lands is also the
170
+ // click whose target disappears. Playwright's default actionability re-check
171
+ // reads that as a lost element ("element was detached from the DOM,
172
+ // retrying") and then spends the rest of its budget waiting for an option
173
+ // that has already done its job and will not come back -- three of the four
174
+ // failures in the 2026-08-31 SaaS 8.10 run ended that way, each on a
175
+ // 30000ms locator.click against an option the log shows it had resolved.
176
+ //
177
+ // So: dispatch the click without the post-click stability re-check, and
178
+ // treat the option list closing as the proof it landed rather than the
179
+ // click call resolving cleanly. Retried as a whole because a click that
180
+ // genuinely missed leaves the menu open, which the next pass sees and
181
+ // re-clicks. This also covers the menu opening with the wanted cluster
182
+ // already selected: clicking it still closes the menu, and the stage the
183
+ // caller asked for is bound either way.
184
+ await (0, test_1.expect)(async () => {
185
+ const menuOpen = await option.isVisible().catch(() => false);
186
+ if (!menuOpen) {
187
+ await this.developmentClusterSelect.click({ timeout: 15000 });
188
+ await (0, test_1.expect)(option).toBeVisible({ timeout: 15000 });
189
+ }
190
+ await option.click({ timeout: 15000, force: true });
191
+ await (0, test_1.expect)(option).toBeHidden({ timeout: 15000 });
192
+ }).toPass({ timeout: 120000, intervals: [1000, 2000, 5000] });
170
193
  await this.defineStagesSaveButton.click({ timeout: 30000 });
171
194
  await (0, test_1.expect)(this.defineStagesDialog).toBeHidden({ timeout: 60000 });
172
195
  }
@@ -50,9 +50,22 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
50
50
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
51
51
  timeout: 120000,
52
52
  });
53
- await modelerCreatePage.clickViewProcessInstanceLink();
54
- const operateTab = await page.waitForEvent('popup', { timeout: 60000 });
53
+ // Latch the Operate instance deep link on the CONTEXT before the click.
54
+ // Playwright raises `popup` only once the new tab's initial response has
55
+ // started loading, and a session-less Operate tab's auth redirect chain
56
+ // drops the instance path before anything commits -- so no listener
57
+ // attached to the popup afterwards ever sees that URL. Without it
58
+ // `waitForInstanceLocator` has nothing to re-open and can only reload
59
+ // whatever the tab was left on, where the instance page can never render:
60
+ // "Assertion of Instance History panel in Operate timed out after
61
+ // 480000ms (instance header not rendered, url=<cluster>/operate?continue,
62
+ // instance deep link never captured)". `openAppTabWithRetry` also
63
+ // registers the popup wait BEFORE the click, which `waitForEvent` after
64
+ // the click cannot. Same pattern as tests/8.10/smoke-tests.spec.ts.
65
+ const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
66
+ const operateTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink);
55
67
  const operateTabProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(operateTab);
68
+ await operateTabProcessInstancePage.rememberInstanceUrl(60000, watchedInstanceUrl);
56
69
  await operateTabProcessInstancePage.closePopOverIfVisible();
57
70
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'Completed icon', 60000);
58
71
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTab, operateTabProcessInstancePage.variablesList, 'Variable list', 30000);
@@ -92,9 +105,12 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
92
105
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
93
106
  timeout: 240000,
94
107
  });
95
- await modelerCreatePage.clickViewProcessInstanceLink();
96
- const operateTab = await page.waitForEvent('popup', { timeout: 60000 });
108
+ // Context-level deep-link latch + click-then-wait ordering, as in the
109
+ // "REST Connector No Auth" flow above -- see the note there.
110
+ const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
111
+ const operateTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink);
97
112
  const operateTabProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(operateTab);
113
+ await operateTabProcessInstancePage.rememberInstanceUrl(60000, watchedInstanceUrl);
98
114
  await operateTabProcessInstancePage.closePopOverIfVisible();
99
115
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'Completed icon', 60000);
100
116
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTab, operateTabProcessInstancePage.variablesList, 'Variable list', 60000);
@@ -328,9 +344,12 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
328
344
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
329
345
  timeout: 120000,
330
346
  });
331
- await modelerCreatePage.clickViewProcessInstanceLink();
332
- const operateTab = await page.waitForEvent('popup');
347
+ // Context-level deep-link latch + click-then-wait ordering, as in the
348
+ // "REST Connector No Auth" flow above -- see the note there.
349
+ const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
350
+ const operateTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink);
333
351
  const operateTabProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(operateTab);
352
+ await operateTabProcessInstancePage.rememberInstanceUrl(60000, watchedInstanceUrl);
334
353
  await operateTabProcessInstancePage.closePopOverIfVisible();
335
354
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'Completed icon', 60000);
336
355
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTab, operateTab.getByTestId('variables-list'), 'Variable list', 30000);
@@ -448,10 +467,12 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
448
467
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
449
468
  timeout: 120000,
450
469
  });
451
- await modelerCreatePage.clickViewProcessInstanceLink();
452
- const operateTabPromise = page.waitForEvent('popup', { timeout: 60000 });
453
- const operateTab = await operateTabPromise;
470
+ // Context-level deep-link latch + click-then-wait ordering, as in the
471
+ // "REST Connector No Auth" flow above -- see the note there.
472
+ const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
473
+ const operateTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink);
454
474
  const operateTabProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(operateTab);
475
+ await operateTabProcessInstancePage.rememberInstanceUrl(60000, watchedInstanceUrl);
455
476
  await operateTabProcessInstancePage.closePopOverIfVisible();
456
477
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'completed icon');
457
478
  // Operate no longer displays document filenames or storage metadata
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1052",
3
+ "version": "0.0.1053",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",