@camunda/e2e-test-suite 0.0.1276 → 0.0.1277
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 +5 -0
- package/dist/pages/8.8/AppsPage.js +231 -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/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
|
|
@@ -105,6 +105,10 @@ _8_8_1.test.describe('Console User Flow Tests @tasklistV2', () => {
|
|
|
105
105
|
});
|
|
106
106
|
await _8_8_1.test.step('Create First Email Alert', async () => {
|
|
107
107
|
await clusterDetailsPage.createAlert(true);
|
|
108
|
+
// Hub's alerts table keeps the recipient, date and trigger count in a
|
|
109
|
+
// collapsed expansion row, so the detail assertions below only have a
|
|
110
|
+
// second row to read once it is expanded.
|
|
111
|
+
await clusterDetailsPage.expandAlerts();
|
|
108
112
|
await test_1.expect
|
|
109
113
|
.poll(async () => clusterDetailsPage.alertsList.count(), {
|
|
110
114
|
timeout: 30000,
|
|
@@ -165,6 +169,7 @@ _8_8_1.test.describe('Console User Flow Tests @tasklistV2', () => {
|
|
|
165
169
|
await (0, test_1.expect)(clusterDetailsPage.alertsList.first()).toBeVisible({
|
|
166
170
|
timeout: 20000,
|
|
167
171
|
});
|
|
172
|
+
await clusterDetailsPage.expandAlerts();
|
|
168
173
|
(0, test_1.expect)(await clusterDetailsPage.alertsList.count()).toBeGreaterThan(1);
|
|
169
174
|
await (0, test_1.expect)(clusterDetailsPage.alertsList.nth(0)).toContainText(`Email`);
|
|
170
175
|
await clusterDetailsPage.assertAlertText('Amount triggered1');
|
|
@@ -205,6 +210,7 @@ _8_8_1.test.describe('Console User Flow Tests @tasklistV2', () => {
|
|
|
205
210
|
await (0, test_1.expect)(clusterDetailsPage.alertsList.first()).toBeVisible({
|
|
206
211
|
timeout: 20000,
|
|
207
212
|
});
|
|
213
|
+
await clusterDetailsPage.expandAlerts();
|
|
208
214
|
(0, test_1.expect)(await clusterDetailsPage.alertsList.count()).toBeGreaterThan(1);
|
|
209
215
|
await clusterDetailsPage.assertAlertText('Amount triggered3');
|
|
210
216
|
}).toPass({ timeout: 240000 });
|
|
@@ -31,7 +31,9 @@ _8_8_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
31
31
|
await (0, mailSlurpClient_1.deleteInbox)(inboxes[testInfo.title]);
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
-
|
|
34
|
+
// Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
|
|
35
|
+
// Also tracked in https://github.com/camunda/product-hub/issues/3655
|
|
36
|
+
_8_8_1.test.skip('Form.js Integration with User Task and AI Generated Form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
|
|
35
37
|
_8_8_1.test.slow();
|
|
36
38
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
37
39
|
const processName = 'User_Task_Process_With_AI_Form' + randomString;
|
|
@@ -88,7 +90,9 @@ _8_8_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
88
90
|
await operateTabTaskDetailsPage.clickCompleteTaskButton();
|
|
89
91
|
});
|
|
90
92
|
});
|
|
91
|
-
|
|
93
|
+
// Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
|
|
94
|
+
// Also tracked in https://github.com/camunda/product-hub/issues/3655
|
|
95
|
+
_8_8_1.test.skip('Form.js Integration with AI generated Start form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
|
|
92
96
|
_8_8_1.test.slow();
|
|
93
97
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
94
98
|
const processName = 'AI-generated-Start-form' + randomString;
|
|
@@ -810,24 +810,51 @@ async function listWorkspaceEnvironments(page, workspaceId) {
|
|
|
810
810
|
// only the new id would silently unassign every other cluster the suite had
|
|
811
811
|
// already linked (the AWS cluster, a sibling Tasklist-mode project's default).
|
|
812
812
|
async function linkClusterToWorkspaceViaApi(page, workspaceId, clusterName) {
|
|
813
|
-
|
|
813
|
+
// Hub discovers a cluster into an environment asynchronously, so a cluster
|
|
814
|
+
// this suite just created via the API is healthy well before its
|
|
815
|
+
// environment exists. A single probe therefore failed specs that create
|
|
816
|
+
// their own cluster: the nightly reported `No environment backs cluster
|
|
817
|
+
// "Second RBA V2 Cluster"` while three OTHER clusters were listed ACTIVE,
|
|
818
|
+
// and `"Fourth RBA V2 Cluster"; available: <none>` at a moment when the
|
|
819
|
+
// list came back empty entirely. Poll until this cluster's environment
|
|
820
|
+
// shows up. The throw below still stands once the deadline passes, so a
|
|
821
|
+
// genuinely absent cluster fails exactly as before -- only the race is
|
|
822
|
+
// absorbed.
|
|
823
|
+
const deadline = Date.now() + 120000;
|
|
824
|
+
let environments = await listOrganizationEnvironments(page);
|
|
814
825
|
if (environments === null) {
|
|
815
826
|
console.log('Environments feature is off for this org; leaving the workspace unlinked');
|
|
816
827
|
return;
|
|
817
828
|
}
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
829
|
+
const pickMatch = (candidates) => {
|
|
830
|
+
const matched = candidates.filter((environment) => environment.clusterName === clusterName);
|
|
831
|
+
// Prefer an ACTIVE environment over a stale NOT_REPORTED duplicate left by
|
|
832
|
+
// an earlier cluster of the same name.
|
|
833
|
+
return (matched.find((environment) => environment.discoveryState === 'ACTIVE') ??
|
|
834
|
+
matched[0]);
|
|
835
|
+
};
|
|
836
|
+
let match = pickMatch(environments);
|
|
837
|
+
while (!match && Date.now() < deadline) {
|
|
838
|
+
await page.waitForTimeout(5000);
|
|
839
|
+
const refreshed = await listOrganizationEnvironments(page);
|
|
840
|
+
if (refreshed === null) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
environments = refreshed;
|
|
844
|
+
match = pickMatch(environments);
|
|
845
|
+
}
|
|
823
846
|
if (!match) {
|
|
824
847
|
throw new Error(`No environment backs cluster "${clusterName}"; available: ` +
|
|
825
848
|
(environments
|
|
826
849
|
.map((e) => `${e.name}@${e.clusterName} (${e.discoveryState})`)
|
|
827
850
|
.join(', ') || '<none>'));
|
|
828
851
|
}
|
|
852
|
+
// Bind to a const so the narrowing survives the closures below: `match` is
|
|
853
|
+
// a `let` reassigned by the discovery poll, so TypeScript widens it back to
|
|
854
|
+
// `HubEnvironment | undefined` inside callbacks.
|
|
855
|
+
const backing = match;
|
|
829
856
|
const assigned = await listWorkspaceEnvironments(page, workspaceId);
|
|
830
|
-
if (assigned.some((environment) => environment.id ===
|
|
857
|
+
if (assigned.some((environment) => environment.id === backing.id)) {
|
|
831
858
|
// Logged rather than returning silently: a quiet no-op here is
|
|
832
859
|
// indistinguishable from the helper never having run at all, which is
|
|
833
860
|
// exactly the ambiguity that hid an earlier bug.
|
|
@@ -845,10 +872,10 @@ async function linkClusterToWorkspaceViaApi(page, workspaceId, clusterName) {
|
|
|
845
872
|
const current = attempt === 1
|
|
846
873
|
? assigned
|
|
847
874
|
: await listWorkspaceEnvironments(page, workspaceId);
|
|
848
|
-
if (attempt > 1 && current.some((e) => e.id ===
|
|
875
|
+
if (attempt > 1 && current.some((e) => e.id === backing.id)) {
|
|
849
876
|
return;
|
|
850
877
|
}
|
|
851
|
-
const ids = [...new Set([...current.map((e) => e.id),
|
|
878
|
+
const ids = [...new Set([...current.map((e) => e.id), backing.id])];
|
|
852
879
|
const response = await clusterApiRequest(page, 'PUT', url, {
|
|
853
880
|
environmentIds: ids,
|
|
854
881
|
});
|
|
@@ -860,8 +887,8 @@ async function linkClusterToWorkspaceViaApi(page, workspaceId, clusterName) {
|
|
|
860
887
|
`${response.status()} ${response.statusText()}`);
|
|
861
888
|
}
|
|
862
889
|
const after = await listWorkspaceEnvironments(page, workspaceId);
|
|
863
|
-
if (after.some((e) => e.id ===
|
|
864
|
-
console.log(`Linked cluster "${clusterName}" (environment ${
|
|
890
|
+
if (after.some((e) => e.id === backing.id)) {
|
|
891
|
+
console.log(`Linked cluster "${clusterName}" (environment ${backing.id}) to workspace ${workspaceId}`);
|
|
865
892
|
return;
|
|
866
893
|
}
|
|
867
894
|
}
|