@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.
- package/dist/pages/8.8/AppsPage.d.ts +4 -0
- package/dist/pages/8.8/AppsPage.js +209 -22
- package/dist/pages/8.8/ClusterDetailsPage.d.ts +9 -0
- package/dist/pages/8.8/ClusterDetailsPage.js +154 -3
- package/dist/pages/8.8/ClusterPage.d.ts +1 -0
- package/dist/pages/8.8/ClusterPage.js +49 -20
- package/dist/pages/8.8/ConnectorMarketplacePage.d.ts +2 -0
- package/dist/pages/8.8/ConnectorMarketplacePage.js +44 -2
- package/dist/pages/8.8/ConsoleOrganizationPage.js +63 -18
- package/dist/pages/8.8/FormJsPage.d.ts +4 -0
- package/dist/pages/8.8/FormJsPage.js +31 -1
- package/dist/pages/8.8/ModelerCreatePage.d.ts +3 -0
- package/dist/pages/8.8/ModelerCreatePage.js +167 -18
- package/dist/pages/8.8/PlayPage.js +14 -3
- package/dist/pages/8.8/SettingsPage.js +19 -2
- package/dist/pages/8.8/UtilitiesPage.js +43 -3
- package/dist/pages/8.8/WorkspaceMembersPage.d.ts +20 -0
- package/dist/pages/8.8/WorkspaceMembersPage.js +258 -0
- package/dist/tests/8.8/access-user-role-management.spec.js +95 -0
- package/dist/tests/8.8/console-user-flows.spec.js +6 -0
- package/dist/tests/8.8/web-modeler-user-flows.spec.js +6 -2
- package/dist/utils/consoleApiHelpers.js +38 -11
- package/package.json +1 -1
|
@@ -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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1504
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
454
|
-
|
|
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 };
|