@camunda/e2e-test-suite 0.0.1276 → 0.0.1278

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.
@@ -10,6 +10,7 @@ const sessionRecovery_1 = require("../../utils/sessionRecovery");
10
10
  const hubResourceRename_1 = require("../../utils/hubResourceRename");
11
11
  const hubDeployDialog_1 = require("../../utils/hubDeployDialog");
12
12
  const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
13
+ const modelerCluster_1 = require("../../utils/modelerCluster");
13
14
  class ModelerCreatePage {
14
15
  page;
15
16
  processApplicationPage;
@@ -82,6 +83,7 @@ class ModelerCreatePage {
82
83
  publicAccessToggle;
83
84
  publicationTab;
84
85
  copyPublicLinkButton;
86
+ publicLinkCodeBlock;
85
87
  closeButton;
86
88
  rbaEnabledDeployedDialog;
87
89
  viewProcessLink;
@@ -168,12 +170,26 @@ class ModelerCreatePage {
168
170
  this.viewProcessInstanceLink = page.getByRole('button', {
169
171
  name: 'View process instance',
170
172
  });
171
- this.viewProcessLink = page.getByRole('button', {
172
- name: 'View process in Operate',
173
- });
173
+ // Hub's deploy no longer raises the Carbon "View process in Operate"
174
+ // action: a nightly trace whose POST /internal/v2/files/<id>/deploy
175
+ // returned 200 contains no such control anywhere in the DOM. Its dialog
176
+ // deploys the whole PROJECT, so the confirmation is worded for the
177
+ // deployed entity instead. Accept either, matching pages/8.7.
178
+ this.viewProcessLink = page
179
+ .getByRole('button', { name: 'View process in Operate' })
180
+ .or(page.getByText('view process in Operate'))
181
+ .or(page.getByText(/^(Diagram|Project|Process application) deployed!$/))
182
+ .first();
174
183
  this.deployErrorMessage = page.getByText('The diagram failed to deploy & run');
175
184
  this.nameInput = page.getByLabel('Name', { exact: true });
176
- this.diagramBreadcrumb = page.locator('[data-test="breadcrumb-diagram"]');
185
+ // Hub emits no breadcrumb-* data-test attributes (a DOM audit of a live
186
+ // run found none of them); its trail is the `hubBreadcrumbs` landmark and
187
+ // the current diagram is its last entry. Keep the legacy id for the
188
+ // pre-design-system shells.
189
+ this.diagramBreadcrumb = page
190
+ .locator('[data-test="breadcrumb-diagram"]')
191
+ .or((0, hubResourceRename_1.hubBreadcrumbs)(page).getByRole('button').last())
192
+ .first();
177
193
  this.renameDiagramNameButton = page.getByText('Rename');
178
194
  // Hub's rename dialog field, with the legacy inline editable kept for
179
195
  // shells where the design-system migration has not shipped.
@@ -224,10 +240,17 @@ class ModelerCreatePage {
224
240
  this.webhookIsActiveButton = page.getByRole('button', {
225
241
  name: 'Webhook is active in Test',
226
242
  });
243
+ // The webhook panel's "Path" is no longer a readonly textbox labelled
244
+ // "Copy to clipboard": Hub renders it as a design-system code block
245
+ // (<div data-slot="code-block"><pre><code>URL</code></pre>) with the copy
246
+ // action as a separate icon button, so only the <code> carries the URL
247
+ // the caller reads with innerText(). `.first()` keeps the "Path" block,
248
+ // which precedes the "Last activities" one in DOM order.
227
249
  this.copyWebhookUrlToClipboardButton = page
228
250
  .getByRole('textbox', {
229
251
  name: 'Copy to clipboard',
230
252
  })
253
+ .or(page.locator('[data-slot="code-block"] code'))
231
254
  .first();
232
255
  this.createTask = page.getByTitle('Create task');
233
256
  this.createEndEvent = page.getByTitle('Create end event');
@@ -240,7 +263,15 @@ class ModelerCreatePage {
240
263
  this.firstPlacedElement = page.locator('g:nth-child(2) > .djs-element > .djs-hit');
241
264
  this.secondPlacedElement = page.locator('g:nth-child(2) > .djs-element > .djs-hit');
242
265
  this.payloadInput = page.locator('[class="fjs-input"]');
243
- this.marketPlaceButton = page.getByTitle('Browse Marketplace for more Connectors');
266
+ // camunda-hub replaced the change-element popup's marketplace icon with a
267
+ // "Browse all" entry in the popup header: a nightly trace's DOM shows
268
+ // <button title="Browse all available elements and assets"> inside
269
+ // .djs-popup-header-group and no "Browse Marketplace ..." node at all.
270
+ // Keep the legacy title for pre-Hub shells. Same shape as pages/8.7.
271
+ this.marketPlaceButton = page
272
+ .getByTitle('Browse Marketplace for more Connectors')
273
+ .or(page.getByTitle('Browse all available elements and assets'))
274
+ .first();
244
275
  // Match either marketplace display name — the connector was renamed from
245
276
  // "Public Holiday Outbound" to "Worldwide Public Holiday"; accepting both
246
277
  // keeps the flow green whichever the marketplace currently serves.
@@ -281,6 +312,13 @@ class ModelerCreatePage {
281
312
  this.copyPublicLinkButton = page.getByRole('button', {
282
313
  name: 'Copy to clipboard',
283
314
  });
315
+ // The Publication tab's link, held by the code block's <code> rather than
316
+ // by the copy button. Filtered on the scheme so it resolves only once the
317
+ // tab has actually loaded a link.
318
+ this.publicLinkCodeBlock = page
319
+ .locator('[data-slot="code-block"] code')
320
+ .filter({ hasText: /^https?:\/\// })
321
+ .first();
284
322
  this.rbaEnabledDeployedDialog = page.getByRole('dialog', {
285
323
  name: 'Process successfully deployed',
286
324
  });
@@ -619,6 +657,13 @@ class ModelerCreatePage {
619
657
  await this.deployMenuItem.click({ timeout: 60000 });
620
658
  }
621
659
  async deployDiagram(clusterName) {
660
+ // Hub's deploy dialog lists the ENVIRONMENTS assigned to this workspace,
661
+ // not the org's clusters, so a cluster created after the workspace (e.g.
662
+ // hto-user-flows' API-created "Public Form V2 Cluster") is simply absent
663
+ // from it and selectCluster reports the stage binding as not taken.
664
+ // runProcessInstance() and FormJsPage.deployForm() already ensure that
665
+ // link at the point of use; this path was the one that did not.
666
+ await (0, consoleApiHelpers_1.ensureClusterLinkedToSharedWorkspace)(this.page, clusterName);
622
667
  await this.processApplicationPage.ensureStageCluster(clusterName);
623
668
  await this.clickDeployButtonWithRetry();
624
669
  await (0, test_1.expect)(this.deployDiagramDialog).toBeVisible({
@@ -722,7 +767,13 @@ class ModelerCreatePage {
722
767
  catch (error) {
723
768
  console.warn(`Attempt ${attempt} failed: ${error instanceof Error ? error.message : error}`);
724
769
  if (attempt === maxRetries) {
725
- throw new Error(`runProcessInstance failed after ${maxRetries} attempts.`);
770
+ // Carry the last attempt's message. Reporting only the attempt
771
+ // count discarded the one thing needed to act on this: the nightly
772
+ // failed here 10 times over and neither the report, the trace's
773
+ // action log nor the network log (no 4xx/5xx at all) retained why,
774
+ // because the per-attempt reason only ever went to console.warn.
775
+ throw new Error(`runProcessInstance failed after ${maxRetries} attempts. ` +
776
+ `Last error: ${error instanceof Error ? error.message : String(error)}`);
726
777
  }
727
778
  await this.page.reload();
728
779
  await this.page.waitForTimeout(retryDelayMs);
@@ -1451,6 +1502,36 @@ class ModelerCreatePage {
1451
1502
  await this.suppressTestModeAnnouncement();
1452
1503
  await this.playTab.click();
1453
1504
  }
1505
+ // Scopes the "Set up test run" deploy step to the diagram under test.
1506
+ // Hub's step 2 ("Choose resources to deploy") is a radio group whose
1507
+ // "All resources" option (`#deploy-mode-bundle`, value "bundle") arrives
1508
+ // pre-selected, so Play deploys the WHOLE shared project -- and a nightly
1509
+ // trace's network log shows the resulting
1510
+ // `POST /api/internal/v2/projects/<id>/deploy` answering 400 on every one
1511
+ // of the ~25 retry attempts, rejected for OTHER specs' resources left in
1512
+ // the project ("'Document_Handling_Connectors_User_Flow_GCP_Process...bpmn':
1513
+ // Element: StartEvent_1 > extensionElements > ioMapping > output - ERROR:
1514
+ // Expected expression but not found"). "Only this resource"
1515
+ // (`#deploy-mode-single-file`) deploys just this process. Matched by id
1516
+ // because the radio button carries no accessible name of its own: the
1517
+ // label text sits in a sibling <div>.
1518
+ async selectPlayDeploySingleResource() {
1519
+ const singleResourceOption = this.page.locator('#deploy-mode-single-file:not([disabled])');
1520
+ const hasOption = await singleResourceOption
1521
+ .waitFor({ state: 'visible', timeout: 15000 })
1522
+ .then(() => true)
1523
+ .catch(() => false);
1524
+ if (!hasOption) {
1525
+ return;
1526
+ }
1527
+ await (0, test_1.expect)(async () => {
1528
+ if ((await singleResourceOption.getAttribute('aria-checked')) === 'true') {
1529
+ return;
1530
+ }
1531
+ await singleResourceOption.click({ timeout: 10000 });
1532
+ await (0, test_1.expect)(singleResourceOption).toHaveAttribute('aria-checked', 'true', { timeout: 10000 });
1533
+ }).toPass({ timeout: 30000 });
1534
+ }
1454
1535
  async completePlayConfiguration(clusterName) {
1455
1536
  // Play is a tab on the diagram page, not its own URL, so the stage-cluster
1456
1537
  // detour's goto() lands back on the Modeler tab and every Play locator
@@ -1500,9 +1581,12 @@ class ModelerCreatePage {
1500
1581
  // only renders visibly while that item is expanded. It expands by itself
1501
1582
  // once the cluster step completes, but a manual heading click is the
1502
1583
  // recovery path if it did not.
1503
- const deployStepHeading = this.page.getByRole('button', {
1504
- name: 'Deploy process',
1505
- });
1584
+ // With Hub's Environments feature on, the panel is "Set up test run" and
1585
+ // this step is headed "Choose resources to deploy".
1586
+ const deployStepHeading = this.page
1587
+ .getByRole('button', { name: 'Deploy process' })
1588
+ .or(this.page.getByRole('button', { name: 'Choose resources to deploy' }))
1589
+ .first();
1506
1590
  // Play settles into exactly ONE of three states, and which one is not
1507
1591
  // knowable up front, so race them instead of timing out on each in turn:
1508
1592
  // the setup overlay, the legacy Continue button, or the configure-test
@@ -1530,6 +1614,11 @@ class ModelerCreatePage {
1530
1614
  .locator('[data-test="test-studio-configuration-overlay"]')
1531
1615
  .or(this.page.locator('[data-test="play-configuration-overlay"]'))
1532
1616
  .or(this.page.getByText('Setup environment'))
1617
+ // With Hub's Environments feature on the card is titled "Set up test
1618
+ // run" and its steps are "Choose where to run" / "Choose resources to
1619
+ // deploy" / "Configure test case" -- a nightly trace's DOM carries that
1620
+ // H5 and none of the three locators above. Same shape as pages/8.7.
1621
+ .or(this.page.getByText('Set up test run', { exact: true }))
1533
1622
  .first();
1534
1623
  const configureTestPanel = this.page.getByTestId('configure-test-panel');
1535
1624
  await (0, test_1.expect)(configurationOverlay
@@ -1550,8 +1639,34 @@ class ModelerCreatePage {
1550
1639
  const connectClusterButton = this.page.getByRole('button', {
1551
1640
  name: 'Connect cluster',
1552
1641
  });
1642
+ // With Hub's Environments feature on, step 1 ("Choose where to run")
1643
+ // offers "Select environment" instead, which opens the "Choose where to
1644
+ // run" dialog listing the workspace's environment rows (camunda-hub
1645
+ // test-studio ClusterStepContent / TestModeDeployEndpointConfigModal).
1646
+ const selectEnvironmentButton = this.page.getByRole('button', {
1647
+ name: 'Select environment',
1648
+ });
1649
+ // The step shows "Verifying cluster availability" before either button
1650
+ // renders, so give it a moment instead of a non-waiting isVisible().
1651
+ await connectClusterButton
1652
+ .or(selectEnvironmentButton)
1653
+ .first()
1654
+ .waitFor({ state: 'visible', timeout: 15000 })
1655
+ .catch(() => { });
1656
+ if (await selectEnvironmentButton.isVisible().catch(() => false)) {
1657
+ await selectEnvironmentButton.click({ timeout });
1658
+ const chooseWhereToRunDialog = this.page.getByRole('dialog', {
1659
+ name: 'Choose where to run',
1660
+ });
1661
+ await (0, test_1.expect)(chooseWhereToRunDialog).toBeVisible({ timeout });
1662
+ await (0, modelerCluster_1.selectDeployEnvironment)(chooseWhereToRunDialog, clusterName);
1663
+ await chooseWhereToRunDialog
1664
+ .getByRole('button', { name: 'Save', exact: true })
1665
+ .click({ timeout });
1666
+ await (0, test_1.expect)(chooseWhereToRunDialog).toBeHidden({ timeout });
1667
+ }
1553
1668
  const needsConnect = await connectClusterButton
1554
- .isVisible({ timeout: 3000 })
1669
+ .isVisible()
1555
1670
  .catch(() => false);
1556
1671
  if (needsConnect) {
1557
1672
  await connectClusterButton.click({ timeout });
@@ -1639,6 +1754,7 @@ class ModelerCreatePage {
1639
1754
  // Wait for enabled; the button stays disabled until the cluster
1640
1755
  // connection is confirmed by the backend after the Save in step 1.
1641
1756
  await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 60000 });
1757
+ await this.selectPlayDeploySingleResource();
1642
1758
  // The click is retried against the completion probe rather than fired
1643
1759
  // once, because the same collapse re-renders the accordion around the
1644
1760
  // button: it can detach mid-click, which is the second nightly
@@ -1719,11 +1835,26 @@ class ModelerCreatePage {
1719
1835
  await this.fillZeebeClientId(zeebeClientId);
1720
1836
  await (0, sleep_1.sleep)(1000);
1721
1837
  }
1838
+ // Opens one of the start event's output-mapping list items. The header
1839
+ // title is a <code> inside `.bio-properties-panel-collapsible-entry-header`
1840
+ // whose click toggles the item, but filling the previous item's FEEL editor
1841
+ // clears/raises its inline "Variable assignment must be defined." error and
1842
+ // re-lays out the list underneath: a nightly trace shows the click on
1843
+ // `<code>zeebeClientId</code>` reported as done while
1844
+ // `data-entry-id="StartEvent_1-output-2"` kept its collapsed class (its
1845
+ // siblings output-0/output-1 both carried `open`), so the editor below
1846
+ // stayed hidden for all 63 polls. Re-drive the toggle until the entry
1847
+ // actually reports open.
1848
+ async openOutputMappingEntry(header, sourceEditor) {
1849
+ await (0, test_1.expect)(async () => {
1850
+ if (!(await sourceEditor.isVisible().catch(() => false))) {
1851
+ await header.click({ timeout: 15000 });
1852
+ }
1853
+ await (0, test_1.expect)(sourceEditor).toBeVisible({ timeout: 10000 });
1854
+ }).toPass({ timeout: 60000, intervals: [1000, 2000, 5000] });
1855
+ }
1722
1856
  async fillZeebeClientId(clientId) {
1723
- await this.zeebeClientIdVariable.click();
1724
- await (0, test_1.expect)(this.variableAssignmentValueTextboxThirdVariable).toBeVisible({
1725
- timeout: 60000,
1726
- });
1857
+ await this.openOutputMappingEntry(this.zeebeClientIdVariable, this.variableAssignmentValueTextboxThirdVariable);
1727
1858
  await this.variableAssignmentValueTextboxThirdVariable.click();
1728
1859
  await (0, sleep_1.sleep)(1000);
1729
1860
  await this.variableAssignmentValueTextboxThirdVariable.fill('');
@@ -1731,10 +1862,7 @@ class ModelerCreatePage {
1731
1862
  await (0, test_1.expect)(this.variableAssignmentValueTextboxThirdVariable).toHaveText(clientId);
1732
1863
  }
1733
1864
  async fillZeebeSecretValue(zeebeSecret) {
1734
- await this.zeebeSecretVariable.click();
1735
- await (0, test_1.expect)(this.variableAssignmentValueTextboxSecondVariable).toBeVisible({
1736
- timeout: 60000,
1737
- });
1865
+ await this.openOutputMappingEntry(this.zeebeSecretVariable, this.variableAssignmentValueTextboxSecondVariable);
1738
1866
  await this.variableAssignmentValueTextboxSecondVariable.click();
1739
1867
  await (0, sleep_1.sleep)(1000);
1740
1868
  await this.variableAssignmentValueTextboxSecondVariable.fill('');
@@ -1809,6 +1937,27 @@ class ModelerCreatePage {
1809
1937
  }
1810
1938
  async getPublicationLink() {
1811
1939
  await this.clickPublicationTab();
1940
+ // Hub renders the publication link as a design-system code block
1941
+ // (<div data-slot="code-block"><pre><code>URL</code></pre>) with the copy
1942
+ // action as a separate icon button, so clicking "Copy to clipboard" no
1943
+ // longer puts the URL where navigator.clipboard.readText() can see it: a
1944
+ // nightly trace has the <code> holding
1945
+ // https://hel-1.tasklist.ultrawombat.com/<uuid>/tasklist/new/<process>
1946
+ // while the test logged "Navigating to public form: " (empty) and then
1947
+ // page.goto('') reloaded the app root, landing on the login screen.
1948
+ // Read the URL out of the DOM, same as pages/8.9. The filter also waits
1949
+ // out the tab's "Updating public process status" spinner, which renders
1950
+ // before the link arrives.
1951
+ const hasCodeBlock = await this.publicLinkCodeBlock
1952
+ .waitFor({ state: 'visible', timeout: 60000 })
1953
+ .then(() => true)
1954
+ .catch(() => false);
1955
+ if (hasCodeBlock) {
1956
+ const link = (await this.publicLinkCodeBlock.innerText()).trim();
1957
+ (0, test_1.expect)(link).toMatch(/^https?:\/\//);
1958
+ console.log('Captured public link:', link);
1959
+ return link;
1960
+ }
1812
1961
  await (0, test_1.expect)(this.copyPublicLinkButton).toBeVisible({ timeout: 60000 });
1813
1962
  await this.clickCopyPublicLinkButton();
1814
1963
  await (0, sleep_1.sleep)(1000);
@@ -12,9 +12,20 @@ class PlayPage {
12
12
  startInstanceOverlayButton;
13
13
  constructor(page) {
14
14
  this.page = page;
15
- this.completeJobButton = page
16
- .getByTestId('diagram')
17
- .getByLabel('Complete job');
15
+ // Hub's Test Studio renders this canvas overlay control as
16
+ // <button data-testid="<elementId>-Complete job"><svg aria-hidden/>
17
+ // <span>Complete job</span></button> -- it carries NO aria-label, so
18
+ // getByLabel() cannot see it (confirmed from a nightly trace: the DOM
19
+ // holds "Complete job" only as the span's text, while the string
20
+ // otherwise appears solely inside our own call log). Its accessible name
21
+ // still comes from that span, so match by role as well and keep
22
+ // getByLabel for the pre-design-system overlay.
23
+ const diagram = page.getByTestId('diagram');
24
+ this.completeJobButton = diagram
25
+ .getByLabel('Complete job')
26
+ .or(diagram.getByRole('button', { name: 'Complete job' }))
27
+ .or(diagram.getByTestId(/-Complete job$/))
28
+ .first();
18
29
  // Newer Modeler Play UI: the "Configure Scenario" floating card hosts a
19
30
  // primary "Start" Carbon button (variants: "Start", "Start with
20
31
  // variables", "Start with a form"). Source:
@@ -7,8 +7,25 @@ class SettingsPage {
7
7
  logoutButton;
8
8
  constructor(page) {
9
9
  this.page = page;
10
- this.openSettingsButton = page.getByLabel('Open Settings');
11
- this.logoutButton = page.getByRole('button', { name: 'Log out' });
10
+ // Hub's design-system shell renders the header's user menu as
11
+ // <button aria-label="User menu, <full name>" aria-haspopup="menu"> --
12
+ // confirmed in a nightly trace's DOM snapshot, where "Open Settings"
13
+ // appears only inside our own call log and nowhere in the page. The
14
+ // legacy Carbon shell still ships useCamundaTools' "Open Settings"
15
+ // button, so accept either. Same shape as pages/8.7.
16
+ this.openSettingsButton = page
17
+ .getByLabel('Open Settings')
18
+ .or(page.locator('[data-slot="user-menu-trigger"]'))
19
+ .or(page.getByRole('button', { name: /^User menu,/ }))
20
+ .first();
21
+ // The design-system user menu is a Radix dropdown, whose entries carry
22
+ // role="menuitem" (camunda-hub's nav-shell-ds.spec.saas.tsx); the legacy
23
+ // Carbon panel renders a plain role="button". `.first()` avoids a
24
+ // strict-mode violation if both shells' controls are ever present.
25
+ this.logoutButton = page
26
+ .getByRole('button', { name: 'Log out' })
27
+ .or(page.getByRole('menuitem', { name: 'Log out' }))
28
+ .first();
12
29
  }
13
30
  async clickOpenSettingsButton(retries = 3) {
14
31
  for (let i = 0; i < retries; i++) {
@@ -317,6 +317,20 @@ async function runProcess(clusterName, page, modelerHomePage, modelerCreatePage,
317
317
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
318
318
  timeout: 60000,
319
319
  });
320
+ // camunda-hub 6813a7e (2026-09-25) made the "Instance started!" toast
321
+ // persistent (shouldPersist) whenever it carries the Operate action, so it
322
+ // no longer times out on its own -- a nightly trace shows the button
323
+ // resolving visible on all 63 polls of the 60s wait below. Close it via its
324
+ // "Dismiss" button. Same handling as pages/8.7.
325
+ const instanceStartedToast = page
326
+ .locator('[data-sonner-toast]')
327
+ .filter({ has: modelerCreatePage.viewProcessInstanceLink });
328
+ const dismissButton = instanceStartedToast.getByRole('button', {
329
+ name: 'Dismiss',
330
+ });
331
+ if (await dismissButton.isVisible().catch(() => false)) {
332
+ await dismissButton.click({ timeout: 10000 });
333
+ }
320
334
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeHidden({
321
335
  timeout: 60000,
322
336
  });
@@ -450,9 +464,35 @@ async function assertTestUsesCorrectOrganization(page) {
450
464
  await (0, test_1.expect)(homePage.modelerBanner).toBeVisible({ timeout: 30000 });
451
465
  }
452
466
  else {
453
- await (0, test_1.expect)(homePage.consoleBanner.or(homePage.hubHeader)).toBeVisible({
454
- timeout: 60000,
455
- });
467
+ // A lost session lands here on Auth0's "Welcome" screen, where no header
468
+ // exists at all -- the nightly screenshot for RBA "No User Permission" is
469
+ // that login card, so the 60s wait below was reporting a missing locator
470
+ // for what is really a sign-in that did not survive. Detect it and say so,
471
+ // rather than blaming the header. (SaaS INT's Auth0 tenant also 429s
472
+ // /u/login/identifier under concurrent CI load, which produces exactly
473
+ // this state.)
474
+ const loginCard = page
475
+ .getByRole('heading', { name: 'Welcome', exact: true })
476
+ .or(page.getByRole('button', { name: 'Continue with Google' }));
477
+ const landed = await homePage.consoleBanner
478
+ .or(homePage.hubHeader)
479
+ .first()
480
+ .waitFor({ state: 'visible', timeout: 60000 })
481
+ .then(() => true)
482
+ .catch(() => false);
483
+ if (!landed) {
484
+ if (await loginCard
485
+ .first()
486
+ .isVisible()
487
+ .catch(() => false)) {
488
+ throw new Error('Session lost: the Auth0 login page is showing, so no Camunda header ' +
489
+ 'can render. The sign-in did not survive (SaaS INT throttles ' +
490
+ '/u/login/identifier under concurrent CI load).');
491
+ }
492
+ await (0, test_1.expect)(homePage.consoleBanner.or(homePage.hubHeader)).toBeVisible({
493
+ timeout: 5000,
494
+ });
495
+ }
456
496
  }
457
497
  // Camunda Hub replaces Console on SaaS (8.10 release notes), and the Hub
458
498
  // shell has neither the "Camunda logo Console" link nor the "Open
@@ -0,0 +1,20 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ type WorkspaceRole = 'Workspace Admin' | 'Editor' | 'Commenter' | 'Viewer';
3
+ declare class WorkspaceMembersPage {
4
+ private page;
5
+ readonly membersTab: Locator;
6
+ readonly manageMembersHeading: Locator;
7
+ readonly addMembersButton: Locator;
8
+ readonly addMemberSearchbox: Locator;
9
+ readonly addMemberSubmitButton: Locator;
10
+ constructor(page: Page);
11
+ showAllWorkspaces(): Promise<void>;
12
+ openManageMembers(workspaceName: string): Promise<void>;
13
+ private memberRow;
14
+ isMember(email: string): Promise<boolean>;
15
+ addMember(email: string, role: WorkspaceRole): Promise<void>;
16
+ private selectRoleIn;
17
+ setMemberRole(email: string, role: WorkspaceRole): Promise<void>;
18
+ removeMember(email: string): Promise<void>;
19
+ }
20
+ export { WorkspaceMembersPage, WorkspaceRole };