@camunda/e2e-test-suite 0.0.1465 → 0.0.1466

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,7 +10,7 @@ declare class BrowseAllConnectorMarketplacePage {
10
10
  constructor(page: Page);
11
11
  isOpen(): Promise<boolean>;
12
12
  waitForConnectorSearchResult(connectorName?: string): Promise<Locator>;
13
- importConnectorToProject(connectorName?: string): Promise<void>;
13
+ importConnectorToProject(connectorName?: string): Promise<boolean>;
14
14
  private waitForInspectionReady;
15
15
  private waitForAnyVisible;
16
16
  private completeImportReviewIfNecessary;
@@ -46,6 +46,9 @@ class BrowseAllConnectorMarketplacePage {
46
46
  }
47
47
  return await this.findConnectorCard(connectorName);
48
48
  }
49
+ // Resolves to whether an "Add" click actually happened. It is false when
50
+ // the project already had the connector ("Added to this project"), in which
51
+ // case the import write path was never exercised.
49
52
  async importConnectorToProject(connectorName) {
50
53
  const connectorCard = await this.waitForConnectorSearchResult(connectorName);
51
54
  await connectorCard.click({ timeout: 60000 });
@@ -60,6 +63,7 @@ class BrowseAllConnectorMarketplacePage {
60
63
  const retryButton = this.marketplaceDetailFooter.getByRole('button', {
61
64
  name: 'Try again',
62
65
  });
66
+ let addClicked = false;
63
67
  for (let attempt = 1; attempt <= 3; attempt++) {
64
68
  await this.waitForInspectionReady(addButton, addedStatus, retryButton);
65
69
  if (await addedStatus.isVisible()) {
@@ -73,6 +77,7 @@ class BrowseAllConnectorMarketplacePage {
73
77
  continue;
74
78
  }
75
79
  await addButton.click({ timeout: 60000 });
80
+ addClicked = true;
76
81
  if (await this.completeImportReviewIfNecessary(addButton, retryButton, addedStatus)) {
77
82
  break;
78
83
  }
@@ -85,6 +90,7 @@ class BrowseAllConnectorMarketplacePage {
85
90
  .getByRole('button', { name: 'Close' })
86
91
  .click({ timeout: 60000 });
87
92
  await browseAllDialog.waitFor({ state: 'hidden', timeout: 60000 });
93
+ return addClicked;
88
94
  }
89
95
  async waitForInspectionReady(addButton, addedStatus, retryButton) {
90
96
  const checkingStatus = this.marketplaceDetailFooter.getByText('Checking this project…', { exact: true });
@@ -164,21 +170,28 @@ class BrowseAllConnectorMarketplacePage {
164
170
  .getByRole('button', {
165
171
  name: new RegExp(`^View details for .*${searchWords}.* from Marketplace$`, 'i'),
166
172
  })
167
- .first();
173
+ .filter({ visible: true });
168
174
  const exactVisible = await exactCard
169
175
  .waitFor({ state: 'visible', timeout: 15000 })
170
176
  .then(() => true)
171
- .catch(() => false);
177
+ .catch((error) => {
178
+ if (error instanceof test_1.errors.TimeoutError) {
179
+ return false;
180
+ }
181
+ throw error;
182
+ });
172
183
  if (exactVisible) {
173
184
  return exactCard;
174
185
  }
175
- try {
176
- await relatedCard.waitFor({ state: 'visible', timeout: 105000 });
177
- return relatedCard;
186
+ await (0, test_1.expect)(relatedCard, `Browse All returned no Marketplace connector matching "${connectorName}".`).not.toHaveCount(0, { timeout: 105000 });
187
+ if (await exactCard.isVisible()) {
188
+ return exactCard;
178
189
  }
179
- catch {
180
- throw new Error(`Browse All returned no Marketplace connector matching "${connectorName}".`);
190
+ const matches = await relatedCard.count();
191
+ if (matches !== 1) {
192
+ throw new Error(`Browse All returned ${matches} Marketplace connectors matching "${connectorName}"; expected one unique match.`);
181
193
  }
194
+ return relatedCard;
182
195
  }
183
196
  }
184
197
  exports.BrowseAllConnectorMarketplacePage = BrowseAllConnectorMarketplacePage;
@@ -12,6 +12,7 @@ declare class ConnectorMarketplacePage {
12
12
  readonly cancelButton: Locator;
13
13
  readonly marketplaceErrorMessage: Locator;
14
14
  readonly noResultsMessage: Locator;
15
+ readonly importErrorMessage: Locator;
15
16
  constructor(page: Page);
16
17
  clickSearchForConnectorTextbox(): Promise<void>;
17
18
  fillSearchForConnectorTextbox(connectorName: string): Promise<void>;
@@ -19,7 +20,8 @@ declare class ConnectorMarketplacePage {
19
20
  clickCancelButton(): Promise<void>;
20
21
  clickReplaceResourceButton(): Promise<void>;
21
22
  waitForConnectorSearchResults(): Promise<void>;
22
- downloadConnectorToProject(): Promise<void>;
23
+ importConnectorAndAssertNoImportError(): Promise<boolean>;
24
+ downloadConnectorToProject(): Promise<boolean>;
23
25
  private confirmImport;
24
26
  private waitForImportToSettle;
25
27
  private getLegacyDownloadButton;
@@ -17,6 +17,7 @@ class ConnectorMarketplacePage {
17
17
  cancelButton;
18
18
  marketplaceErrorMessage;
19
19
  noResultsMessage;
20
+ importErrorMessage;
20
21
  constructor(page) {
21
22
  this.page = page;
22
23
  this.browseAllConnectorMarketplacePage =
@@ -42,6 +43,12 @@ class ConnectorMarketplacePage {
42
43
  this.saveAsCopyButton = page.getByRole('button', { name: 'Save as copy' });
43
44
  this.marketplaceErrorMessage = page.getByText('an error occurred, please try again later');
44
45
  this.noResultsMessage = page.getByText('find a match for your search phrase');
46
+ // Hub's error when a Marketplace import fails, e.g. "Couldn't add this
47
+ // connector ... partial import removed" when an entry carries several
48
+ // versions of one template. The apostrophe may be straight or typographic.
49
+ this.importErrorMessage = page
50
+ .getByText(/couldn.t add this connector/i)
51
+ .first();
45
52
  }
46
53
  async clickSearchForConnectorTextbox() {
47
54
  await this.searchForConnectorTextbox.click({ timeout: 60000 });
@@ -90,14 +97,35 @@ class ConnectorMarketplacePage {
90
97
  }
91
98
  throw new Error('Marketplace connector search failed after retries due to persistent error or no matching results');
92
99
  }
100
+ // Imports the connector like downloadConnectorToProject, but fails with
101
+ // Hub's own import error instead of the generic "could not import after 3
102
+ // attempts" when the import is rejected, and asserts the error is not shown
103
+ // once the import is done.
104
+ // Resolves to whether the import was actually performed (see
105
+ // downloadConnectorToProject).
106
+ async importConnectorAndAssertNoImportError() {
107
+ let imported;
108
+ try {
109
+ imported = await this.downloadConnectorToProject();
110
+ }
111
+ catch (error) {
112
+ if (await this.importErrorMessage.isVisible()) {
113
+ throw new Error(`Hub rejected the Marketplace import of "${this.connectorName}" ("Couldn't add this connector"). Original error: ${error}`);
114
+ }
115
+ throw error;
116
+ }
117
+ await (0, test_1.expect)(this.importErrorMessage).toBeHidden({ timeout: 5000 });
118
+ return imported;
119
+ }
120
+ // Resolves to whether an import write was actually attempted: false when
121
+ // Browse all reported the connector as already added to the project.
93
122
  async downloadConnectorToProject() {
94
123
  if (await this.browseAllConnectorMarketplacePage.isOpen()) {
95
- await this.browseAllConnectorMarketplacePage.importConnectorToProject(this.connectorName);
96
- return;
124
+ return await this.browseAllConnectorMarketplacePage.importConnectorToProject(this.connectorName);
97
125
  }
98
126
  await this.clickDownloadToProjectButton();
99
127
  if (await this.confirmImport()) {
100
- return;
128
+ return true;
101
129
  }
102
130
  // The modal is up but rendered none of the known import actions. Dismiss it
103
131
  // so it does not block the next click on the diagram, then fail here:
@@ -111,6 +139,7 @@ class ConnectorMarketplacePage {
111
139
  // Hub imported without asking for confirmation, so the write is in flight
112
140
  // here too.
113
141
  await this.waitForImportToSettle();
142
+ return true;
114
143
  }
115
144
  // Hub's import modal labels its primary button after the project state:
116
145
  // "Add to project" when the template is new to the project, "Replace
@@ -38,6 +38,10 @@ declare class ModelerCreatePage {
38
38
  readonly restConnectorOption: Locator;
39
39
  readonly changeElementSearchInput: Locator;
40
40
  readonly changeElementResults: Locator;
41
+ readonly aiAgentTaskConnectorOption: Locator;
42
+ readonly aiAgentSubProcessConnectorOption: Locator;
43
+ readonly adHocSubProcessOption: Locator;
44
+ readonly propertiesPanel: Locator;
41
45
  readonly reusableAssetsTab: Locator;
42
46
  readonly marketPlaceButton: Locator;
43
47
  readonly clientIdTextbox: Locator;
@@ -120,6 +124,7 @@ declare class ModelerCreatePage {
120
124
  fillElementIdInput(id: string): Promise<void>;
121
125
  clickStartEventElement(): Promise<void>;
122
126
  selectStartEventElement(): Promise<void>;
127
+ appendTaskToStartEvent(): Promise<string>;
123
128
  private clickCreatePadEntry;
124
129
  private appendViaPopup;
125
130
  clickAppendTaskButton(): Promise<void>;
@@ -167,7 +172,11 @@ declare class ModelerCreatePage {
167
172
  revealChangeElementTemplate(option: Locator, searchTerm: string): Promise<boolean>;
168
173
  private isVisibleWithin;
169
174
  private openChangeElementPopup;
175
+ openChangeElementPopupForElement(elementId: string): Promise<void>;
170
176
  private clickChangeElementTemplateOption;
177
+ assertChangeElementTemplateListed(option: Locator, searchTerm: string, elementId: string): Promise<void>;
178
+ changeElementToAdHocSubProcess(elementId: string): Promise<void>;
179
+ applyAiAgentSubProcessTemplate(): Promise<void>;
171
180
  clickWebhookMessageStartEventConnectorOption(): Promise<void>;
172
181
  clickWebhookIdInput(): Promise<void>;
173
182
  clearWebhookIdInput(): Promise<void>;
@@ -45,6 +45,10 @@ class ModelerCreatePage {
45
45
  restConnectorOption;
46
46
  changeElementSearchInput;
47
47
  changeElementResults;
48
+ aiAgentTaskConnectorOption;
49
+ aiAgentSubProcessConnectorOption;
50
+ adHocSubProcessOption;
51
+ propertiesPanel;
48
52
  reusableAssetsTab;
49
53
  marketPlaceButton;
50
54
  clientIdTextbox;
@@ -227,8 +231,19 @@ class ModelerCreatePage {
227
231
  this.restConnectorOption = page
228
232
  .locator('.djs-popup-results')
229
233
  .locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
234
+ // Require the supported 8.10 template, not the deprecated v1 variant.
235
+ this.aiAgentSubProcessConnectorOption = page
236
+ .locator('.djs-popup-results')
237
+ .locator('[data-id="replace.template-io.camunda.connectors.agenticai.ai-agent-subprocess.v2"]');
238
+ this.adHocSubProcessOption = page
239
+ .locator('.djs-popup-results')
240
+ .locator('[data-id="replace-with-ad-hoc-subprocess"]');
241
+ this.propertiesPanel = page.locator('[data-test="properties-panel"]');
230
242
  this.changeElementSearchInput = page.locator('.djs-popup-search input');
231
243
  this.changeElementResults = page.locator('.djs-popup-results');
244
+ this.aiAgentTaskConnectorOption = page
245
+ .locator('.djs-popup-results')
246
+ .locator('[data-id="replace.template-io.camunda.connectors.agenticai.ai-agent-task.v2"]');
232
247
  // The tab renders its label as text content, and the product source
233
248
  // spells it both "Reusable Assets" and "Reusable assets".
234
249
  this.reusableAssetsTab = page.getByRole('tab', {
@@ -604,6 +619,27 @@ class ModelerCreatePage {
604
619
  async selectStartEventElement() {
605
620
  await this.startEventElement.click();
606
621
  }
622
+ async appendTaskToStartEvent() {
623
+ const startEvent = this.page.locator('.djs-shape[data-element-id="StartEvent_1"]');
624
+ await (0, test_1.expect)(startEvent).toBeVisible({ timeout: 30000 });
625
+ // Renaming can refresh canvas selection; clicking a selected shape toggles it off.
626
+ await (0, test_1.expect)(async () => {
627
+ if (!(await startEvent.evaluate((element) => element.classList.contains('selected')))) {
628
+ await startEvent.click();
629
+ }
630
+ await (0, test_1.expect)(startEvent).toHaveClass(/selected/);
631
+ }).toPass({ timeout: 30000 });
632
+ await this.page
633
+ .getByRole('button', { name: 'Append task', exact: true })
634
+ .click();
635
+ const task = this.page.locator('.djs-shape.selected');
636
+ await (0, test_1.expect)(task).not.toHaveAttribute('data-element-id', 'StartEvent_1');
637
+ const elementId = await task.getAttribute('data-element-id');
638
+ if (!elementId) {
639
+ throw new Error('The appended task has no element ID.');
640
+ }
641
+ return elementId;
642
+ }
607
643
  // The create pad's direct append icons no longer carry the bpmn-js `title`
608
644
  // attributes ("Append task", "Append intermediate/boundary", "Append end
609
645
  // event"), so append through the "append anything" entry instead: it opens
@@ -771,6 +807,7 @@ class ModelerCreatePage {
771
807
  return;
772
808
  }
773
809
  }
810
+ throw new Error(`The diagram did not commit Camunda version ${version}.`);
774
811
  }
775
812
  async clickChangeTypeButton() {
776
813
  try {
@@ -1331,9 +1368,26 @@ class ModelerCreatePage {
1331
1368
  // bare 30s force-click here has no recovery and loses the reopen race on a
1332
1369
  // freshly installed cluster.
1333
1370
  await (0, test_1.expect)(element).toBeVisible({ timeout: 60000 });
1334
- await element.click({ timeout: 30000 });
1371
+ if (!(await element.evaluate((shape) => shape.classList.contains('selected')))) {
1372
+ await element.click({ timeout: 30000 });
1373
+ }
1335
1374
  await this.clickChangeTypeButton();
1336
1375
  }
1376
+ async openChangeElementPopupForElement(elementId) {
1377
+ const element = this.page.locator(`.djs-shape[data-element-id="${elementId}"]`);
1378
+ // A visible popup may belong to another shape or to the append action.
1379
+ await this.page.keyboard.press('Escape');
1380
+ await (0, test_1.expect)(this.changeElementSearchInput).toBeHidden({ timeout: 30000 });
1381
+ await (0, test_1.expect)(element).toBeVisible({ timeout: 30000 });
1382
+ await (0, test_1.expect)(async () => {
1383
+ if (!(await element.evaluate((shape) => shape.classList.contains('selected')))) {
1384
+ await element.click({ timeout: 5000 });
1385
+ }
1386
+ await (0, test_1.expect)(element).toHaveClass(/(?:^|\s)selected(?:\s|$)/);
1387
+ }).toPass({ timeout: 30000 });
1388
+ await this.changeTypeButton.click({ timeout: 30000 });
1389
+ await (0, test_1.expect)(this.changeElementSearchInput).toBeVisible({ timeout: 30000 });
1390
+ }
1337
1391
  async clickChangeElementTemplateOption(option, element, searchTerm) {
1338
1392
  const maxRetries = 3;
1339
1393
  let lastError;
@@ -1360,6 +1414,51 @@ class ModelerCreatePage {
1360
1414
  }
1361
1415
  throw new Error(`Failed to click the connector template after ${maxRetries} attempts: ${lastError}`);
1362
1416
  }
1417
+ // Reopens the change-element popup on the appended element (the Marketplace
1418
+ // import closes it) and asserts that `option` is listed, i.e. that the
1419
+ // imported template really landed in the project.
1420
+ async assertChangeElementTemplateListed(option, searchTerm, elementId) {
1421
+ await this.openChangeElementPopupForElement(elementId);
1422
+ const revealed = await this.revealChangeElementTemplate(option, searchTerm);
1423
+ (0, test_1.expect)(revealed, `"${searchTerm}" was not listed in the change-element popup after the Marketplace import.`).toBe(true);
1424
+ await (0, test_1.expect)(option).toBeVisible({ timeout: 30000 });
1425
+ await (0, test_1.expect)(option).toBeEnabled({ timeout: 30000 });
1426
+ }
1427
+ // Turns the appended task into an ad-hoc sub-process through the
1428
+ // change-element popup. The popup is filtered first: it virtualizes its list.
1429
+ async changeElementToAdHocSubProcess(elementId) {
1430
+ const element = this.page.locator(`.djs-shape[data-element-id="${elementId}"]`);
1431
+ await this.openChangeElementPopupForElement(elementId);
1432
+ await (0, test_1.expect)(this.changeElementSearchInput).toBeVisible({ timeout: 30000 });
1433
+ await this.changeElementSearchInput.click();
1434
+ await this.changeElementSearchInput.fill('');
1435
+ await this.changeElementSearchInput.pressSequentially('Ad-hoc', {
1436
+ delay: 50,
1437
+ });
1438
+ await this.adHocSubProcessOption.click({ timeout: 30000 });
1439
+ await (0, test_1.expect)(element.locator('[data-marker="adhoc"]')).toBeVisible({
1440
+ timeout: 30000,
1441
+ });
1442
+ }
1443
+ // Applies the listed AI Agent Sub-process template to the selected ad-hoc
1444
+ // sub-process and asserts the properties panel resolved it.
1445
+ async applyAiAgentSubProcessTemplate() {
1446
+ await this.aiAgentSubProcessConnectorOption.click({ timeout: 30000 });
1447
+ await this.changeElementSearchInput.fill('');
1448
+ await this.changeElementSearchInput.pressSequentially('OpenAI API', {
1449
+ delay: 50,
1450
+ });
1451
+ const popup = this.page.locator('.djs-popup');
1452
+ await popup
1453
+ .getByRole('option', { name: 'OpenAI API', exact: true })
1454
+ .click({ timeout: 30000 });
1455
+ await (0, test_1.expect)(popup).toBeHidden({ timeout: 30000 });
1456
+ await (0, test_1.expect)(this.page
1457
+ .locator('.bio-properties-panel-header')
1458
+ .getByText('AI Agent Sub-process', { exact: true })).toBeVisible({ timeout: 60000 });
1459
+ await (0, test_1.expect)(this.propertiesPanel.getByText('Applied', { exact: true })).toBeVisible({ timeout: 30000 });
1460
+ await (0, test_1.expect)(this.templateNotFoundTag).toBeHidden({ timeout: 10000 });
1461
+ }
1363
1462
  async clickWebhookMessageStartEventConnectorOption() {
1364
1463
  await this.clickChangeElementTemplateOption(this.webhookMessageStartEventConnectorOption, this.startEventElement, 'Webhook');
1365
1464
  }
@@ -7,6 +7,8 @@ declare class ModelerHomePage {
7
7
  private processApplicationName?;
8
8
  private workspaceProjectsUrl?;
9
9
  private workspacesOverviewUrl?;
10
+ private disposableWorkspaceUrl?;
11
+ private disposableWorkspaceName?;
10
12
  readonly modelerPageBanner: Locator;
11
13
  readonly createNewProjectButton: Locator;
12
14
  readonly createWorkspaceButton: Locator;
@@ -65,6 +67,8 @@ declare class ModelerHomePage {
65
67
  password: string;
66
68
  }): Promise<void>;
67
69
  private ensureWorkspaceOpen;
70
+ createDisposableWorkspace(name: string): Promise<void>;
71
+ deleteDisposableWorkspace(): Promise<void>;
68
72
  private gotoWorkspacesOverview;
69
73
  private createWorkspace;
70
74
  private submitWorkspaceWizard;
@@ -30,6 +30,8 @@ class ModelerHomePage {
30
30
  // re-walk the hierarchy from the root each time.
31
31
  workspaceProjectsUrl;
32
32
  workspacesOverviewUrl;
33
+ disposableWorkspaceUrl;
34
+ disposableWorkspaceName;
33
35
  modelerPageBanner;
34
36
  createNewProjectButton;
35
37
  // /workspaces overview controls (flag-on). The "Create workspace" CTA is
@@ -211,6 +213,7 @@ class ModelerHomePage {
211
213
  // sibling options chevron whose name would otherwise substring-match.
212
214
  this.createWorkspaceButton = page.getByRole('button', {
213
215
  name: 'Create workspace',
216
+ exact: true,
214
217
  });
215
218
  this.workspaceOpenButton = page
216
219
  .getByRole('button', { name: /^open workspace /i })
@@ -479,6 +482,60 @@ class ModelerHomePage {
479
482
  }
480
483
  }
481
484
  }
485
+ // Creates a new, randomly named Workspace and leaves the browser on its
486
+ // projects listing. Template IDs are unique per Workspace, so a connector
487
+ // import into a fresh one always exercises the write path instead of finding
488
+ // the connector already added by an earlier run.
489
+ async createDisposableWorkspace(name) {
490
+ await this.gotoWorkspacesOverview();
491
+ await this.waitForModelerReady();
492
+ await this.clickMessageBanner();
493
+ await this.dismissModals();
494
+ this.disposableWorkspaceName = name;
495
+ try {
496
+ await this.createWorkspace(name);
497
+ }
498
+ finally {
499
+ const current = new URL(this.page.url());
500
+ if (/\/workspaces?\/[^/]+\/projects$/.test(current.pathname)) {
501
+ this.disposableWorkspaceUrl = `${current.origin}${current.pathname}`;
502
+ }
503
+ }
504
+ }
505
+ async deleteDisposableWorkspace() {
506
+ if (!this.disposableWorkspaceName) {
507
+ return;
508
+ }
509
+ if (this.disposableWorkspaceUrl) {
510
+ await this.page.goto(this.disposableWorkspaceUrl.replace(/\/projects$/, '/manage?tab=general'), { waitUntil: 'domcontentloaded', timeout: 30000 });
511
+ }
512
+ else {
513
+ await this.gotoWorkspacesOverview();
514
+ await this.waitForModelerReady(30000);
515
+ const manage = this.page.getByRole('button', {
516
+ name: `Manage workspace ${this.disposableWorkspaceName}`,
517
+ exact: true,
518
+ });
519
+ await (0, test_1.expect)(manage).toBeVisible({ timeout: 15000 });
520
+ await manage.click({ timeout: 15000 });
521
+ await this.page
522
+ .getByRole('tab', { name: 'General', exact: true })
523
+ .click({ timeout: 15000 });
524
+ }
525
+ await this.page
526
+ .getByRole('tabpanel', { name: 'General', exact: true })
527
+ .getByRole('button', { name: 'Delete', exact: true })
528
+ .click({ timeout: 30000 });
529
+ const dialog = this.page.getByRole('dialog').filter({
530
+ has: this.page.getByRole('heading', { name: /^Delete workspace(?:\s|$)/ }),
531
+ });
532
+ await dialog
533
+ .getByRole('button', { name: 'Delete workspace', exact: true })
534
+ .click({ timeout: 30000 });
535
+ await this.page.waitForURL(/\/workspaces\/?$/, { timeout: 30000 });
536
+ this.disposableWorkspaceUrl = undefined;
537
+ this.disposableWorkspaceName = undefined;
538
+ }
482
539
  // Navigates to the Workspaces overview by resolving it against the Modeler app
483
540
  // root in the current URL, so it works whether Modeler is served under
484
541
  // `/modeler` (Self-Managed combined ingress) or at the app root.
@@ -2,10 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const test_1 = require("@playwright/test");
4
4
  const SM_8_10_1 = require("../../fixtures/SM-8.10");
5
+ const ModelerHomePage_1 = require("../../pages/SM-8.10/ModelerHomePage");
5
6
  const _setup_1 = require("../../test-setup.js");
6
7
  const UtilitiesPage_1 = require("../../pages/SM-8.10/UtilitiesPage");
7
8
  const sleep_1 = require("../../utils/sleep");
8
9
  const constants_1 = require("../../utils/constants");
10
+ const test = SM_8_10_1.test.extend({
11
+ modelerHomePage: [
12
+ async ({ page }, use) => {
13
+ const home = new ModelerHomePage_1.ModelerHomePage(page);
14
+ try {
15
+ await use(home);
16
+ }
17
+ finally {
18
+ await home.deleteDisposableWorkspace();
19
+ }
20
+ },
21
+ { scope: 'test', timeout: 120000 },
22
+ ],
23
+ });
9
24
  // Serial: the two active webhook tests both fall back to importing the
10
25
  // "Webhook Connector" marketplace package (which bundles the Boundary,
11
26
  // Intermediate and Message-Start template variants) when the template is not
@@ -17,31 +32,31 @@ const constants_1 = require("../../utils/constants");
17
32
  // Running them serially lets the first import register the templates
18
33
  // Workspace-wide; the second test then reveals them directly and skips the
19
34
  // import, so there is no collision.
20
- SM_8_10_1.test.describe.configure({ mode: 'serial' });
21
- SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
35
+ test.describe.configure({ mode: 'serial' });
36
+ test.describe('Connectors User Flow Tests @tasklistV2', () => {
22
37
  const mtTenant = process.env.IS_MT === 'true' ? '<default>' : '';
23
- SM_8_10_1.test.beforeEach(async ({ navigationPage }, testInfo) => {
38
+ test.beforeEach(async ({ navigationPage }, testInfo) => {
24
39
  await navigationPage.goToModeler((testInfo.workerIndex + 1) * 1000);
25
40
  });
26
- SM_8_10_1.test.afterEach(async ({ page }, testInfo) => {
41
+ test.afterEach(async ({ page }, testInfo) => {
27
42
  await (0, _setup_1.captureScreenshot)(page, testInfo);
28
43
  await (0, _setup_1.captureFailureVideo)(page, testInfo);
29
44
  });
30
45
  // Skipped due to bug #7716: https://github.com/camunda/connectors/issues/7716
31
- SM_8_10_1.test.skip('REST Connector No Auth User Flow', async ({ page, operateHomePage, modelerHomePage, operateProcessInstancePage, modelerCreatePage, connectorSettingsPage, operateProcessesPage, connectorMarketplacePage, navigationPage, }) => {
32
- SM_8_10_1.test.slow();
46
+ test.skip('REST Connector No Auth User Flow', async ({ page, operateHomePage, modelerHomePage, operateProcessInstancePage, modelerCreatePage, connectorSettingsPage, operateProcessesPage, connectorMarketplacePage, navigationPage, }) => {
47
+ test.slow();
33
48
  const processName = 'REST_Connector_No_Auth_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
34
- await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
49
+ await test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
35
50
  await modelerHomePage.clickCrossComponentProjectFolder();
36
51
  await modelerHomePage.clickDiagramTypeDropdown();
37
52
  await modelerHomePage.clickBpmnTemplateOption();
38
53
  });
39
- await SM_8_10_1.test.step('Create BPMN Diagram with REST Connector and Start Process Instance', async () => {
54
+ await test.step('Create BPMN Diagram with REST Connector and Start Process Instance', async () => {
40
55
  await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, connectorMarketplacePage, processName, constants_1.REST_MOCK_URL, 'noAuth', 'body');
41
56
  await modelerCreatePage.runProcessInstance('', mtTenant);
42
57
  await (0, sleep_1.sleep)(20000);
43
58
  });
44
- await SM_8_10_1.test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
59
+ await test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
45
60
  await navigationPage.goToOperate();
46
61
  await operateHomePage.clickProcessesTab();
47
62
  await operateProcessesPage.clickProcessInstanceLink(processName, 'completed');
@@ -56,21 +71,21 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
56
71
  });
57
72
  });
58
73
  });
59
- (0, SM_8_10_1.test)('REST Connector Bearer Token Auth User Flow', async ({ page, operateHomePage, modelerHomePage, modelerCreatePage, connectorSettingsPage, navigationPage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, }) => {
60
- SM_8_10_1.test.slow();
74
+ test('REST Connector Bearer Token Auth User Flow', async ({ page, operateHomePage, modelerHomePage, modelerCreatePage, connectorSettingsPage, navigationPage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, }) => {
75
+ test.slow();
61
76
  const processName = 'REST_Connector_Bearer_Auth_Process' +
62
77
  (await (0, _setup_1.generateRandomStringAsync)(3));
63
- await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
78
+ await test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
64
79
  await modelerHomePage.clickCrossComponentProjectFolder();
65
80
  await modelerHomePage.clickDiagramTypeDropdown();
66
81
  await modelerHomePage.clickBpmnTemplateOption();
67
82
  });
68
- await SM_8_10_1.test.step('Create BPMN Diagram with REST Connector with Basic Auth and Start Process Instance', async () => {
83
+ await test.step('Create BPMN Diagram with REST Connector with Basic Auth and Start Process Instance', async () => {
69
84
  await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, connectorMarketplacePage, processName, constants_1.REST_MOCK_URL, 'bearer', '{message:response.body.message}');
70
85
  await modelerCreatePage.runProcessInstance('', mtTenant);
71
86
  await (0, sleep_1.sleep)(20000);
72
87
  });
73
- await SM_8_10_1.test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
88
+ await test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
74
89
  await navigationPage.goToOperate();
75
90
  await operateHomePage.clickProcessesTab();
76
91
  await operateProcessesPage.clickProcessInstanceLink(processName, 'completed');
@@ -85,16 +100,16 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
85
100
  });
86
101
  });
87
102
  });
88
- (0, SM_8_10_1.test)('Message Start Event Webhook Connector No Auth User Flow', async ({ modelerHomePage, navigationPage, modelerCreatePage, request, operateHomePage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, connectorSettingsPage, }) => {
89
- SM_8_10_1.test.slow();
103
+ test('Message Start Event Webhook Connector No Auth User Flow', async ({ modelerHomePage, navigationPage, modelerCreatePage, request, operateHomePage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, connectorSettingsPage, }) => {
104
+ test.slow();
90
105
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
91
106
  const processName = 'Start_Event_Webhook_Connector_No_Auth_Process' + randomString;
92
- await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
107
+ await test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
93
108
  await modelerHomePage.clickCrossComponentProjectFolder();
94
109
  await modelerHomePage.clickDiagramTypeDropdown();
95
110
  await modelerHomePage.clickBpmnTemplateOption();
96
111
  });
97
- await SM_8_10_1.test.step('Create BPMN Diagram with REST Connector and Deploy Diagram', async () => {
112
+ await test.step('Create BPMN Diagram with REST Connector and Deploy Diagram', async () => {
98
113
  await (0, test_1.expect)(modelerCreatePage.generalPanel).toBeVisible({
99
114
  timeout: 60000,
100
115
  });
@@ -133,7 +148,7 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
133
148
  await modelerCreatePage.deployProcessInstance(mtTenant);
134
149
  await (0, sleep_1.sleep)(20000);
135
150
  });
136
- await SM_8_10_1.test.step('Make Authorization Request', async () => {
151
+ await test.step('Make Authorization Request', async () => {
137
152
  const baseURL = process.env.PLAYWRIGHT_BASE_URL ||
138
153
  `http://gke-${process.env.BASE_URL}.ci.distro.ultrawombat.com`;
139
154
  const url = `${baseURL}/connectors/inbound/test-webhook-id${randomString}`;
@@ -154,7 +169,7 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
154
169
  throw new Error(`Webhook request failed with status ${response?.status()} after ${attempt} attempts`);
155
170
  }
156
171
  });
157
- await SM_8_10_1.test.step('Assert Diagram Has Successfully Completed in Operate', async () => {
172
+ await test.step('Assert Diagram Has Successfully Completed in Operate', async () => {
158
173
  await navigationPage.goToOperate();
159
174
  await operateHomePage.clickProcessesTab();
160
175
  await operateProcessesPage.clickProcessInstanceLink(processName, 'completed');
@@ -163,15 +178,15 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
163
178
  });
164
179
  });
165
180
  // Skipped due to bug #7716: https://github.com/camunda/connectors/issues/7716
166
- SM_8_10_1.test.skip('Connector Secrets User Flow', async ({ operateHomePage, modelerHomePage, navigationPage, modelerCreatePage, connectorSettingsPage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, }) => {
167
- SM_8_10_1.test.slow();
181
+ test.skip('Connector Secrets User Flow', async ({ operateHomePage, modelerHomePage, navigationPage, modelerCreatePage, connectorSettingsPage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, }) => {
182
+ test.slow();
168
183
  const processName = 'REST_Connector_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
169
- await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
184
+ await test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
170
185
  await modelerHomePage.clickCrossComponentProjectFolder();
171
186
  await modelerHomePage.clickDiagramTypeDropdown();
172
187
  await modelerHomePage.clickBpmnTemplateOption();
173
188
  });
174
- await SM_8_10_1.test.step('Create BPMN Diagram with REST Connector using secrets and Start Process Instance', async () => {
189
+ await test.step('Create BPMN Diagram with REST Connector using secrets and Start Process Instance', async () => {
175
190
  await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, connectorMarketplacePage, processName, constants_1.REST_MOCK_BASIC_AUTH_URL, 'basic', '{message:response.body.message}', {
176
191
  resultVariable: 'result',
177
192
  basicAuthCredentials: {
@@ -182,7 +197,7 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
182
197
  await modelerCreatePage.runProcessInstance('', mtTenant);
183
198
  await (0, sleep_1.sleep)(20000);
184
199
  });
185
- await SM_8_10_1.test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
200
+ await test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
186
201
  await navigationPage.goToOperate();
187
202
  await operateHomePage.clickProcessesTab();
188
203
  await operateProcessesPage.clickProcessInstanceLink(processName, 'completed');
@@ -192,16 +207,16 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
192
207
  await operateProcessInstancePage.assertProcessVariableContainsText('message', '"Message from Mock!"');
193
208
  });
194
209
  });
195
- (0, SM_8_10_1.test)('Intermediate Event Webhook Connector No Auth User Flow', async ({ modelerHomePage, modelerCreatePage, request, operateHomePage, operateProcessInstancePage, operateProcessesPage, navigationPage, connectorMarketplacePage, connectorSettingsPage, }) => {
196
- SM_8_10_1.test.slow();
210
+ test('Intermediate Event Webhook Connector No Auth User Flow', async ({ modelerHomePage, modelerCreatePage, request, operateHomePage, operateProcessInstancePage, operateProcessesPage, navigationPage, connectorMarketplacePage, connectorSettingsPage, }) => {
211
+ test.slow();
197
212
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
198
213
  const processName = 'Intermediate_Event_Webhook_Connector_No_Auth_Process' + randomString;
199
- await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
214
+ await test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
200
215
  await modelerHomePage.clickCrossComponentProjectFolder();
201
216
  await modelerHomePage.clickDiagramTypeDropdown();
202
217
  await modelerHomePage.clickBpmnTemplateOption();
203
218
  });
204
- await SM_8_10_1.test.step('Create BPMN Diagram with Intermediate Event Webhook Connector and Run Diagram', async () => {
219
+ await test.step('Create BPMN Diagram with Intermediate Event Webhook Connector and Run Diagram', async () => {
205
220
  await (0, test_1.expect)(modelerCreatePage.generalPanel).toBeVisible({
206
221
  timeout: 120000,
207
222
  });
@@ -251,7 +266,7 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
251
266
  await modelerCreatePage.runProcessInstance('', mtTenant);
252
267
  await (0, sleep_1.sleep)(20000);
253
268
  });
254
- await SM_8_10_1.test.step('Make Authorization Request', async () => {
269
+ await test.step('Make Authorization Request', async () => {
255
270
  const baseURL = process.env.PLAYWRIGHT_BASE_URL ||
256
271
  `http://gke-${process.env.BASE_URL}.ci.distro.ultrawombat.com`;
257
272
  const url = `${baseURL}/connectors/inbound/test-webhook-intermediate${randomString}`;
@@ -272,7 +287,7 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
272
287
  }
273
288
  await (0, sleep_1.sleep)(60000);
274
289
  });
275
- await SM_8_10_1.test.step('Assert Diagram Has Successfully Completed in Operate', async () => {
290
+ await test.step('Assert Diagram Has Successfully Completed in Operate', async () => {
276
291
  await navigationPage.goToOperate();
277
292
  await operateHomePage.clickProcessesTab();
278
293
  await operateProcessesPage.clickProcessInstanceLink(processName, 'completed');
@@ -280,14 +295,70 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
280
295
  (0, test_1.expect)(result).toBe('completed');
281
296
  });
282
297
  });
298
+ // Template IDs are unique per Workspace. A fresh Workspace forces each test
299
+ // through the import write instead of accepting an already-added connector.
300
+ // The fixture deletes it after screenshot hooks, even on failure or timeout.
301
+ const importAiAgentFromMarketplace = async ({ modelerHomePage, modelerCreatePage, connectorMarketplacePage, }, processName) => {
302
+ await test.step('Create a Workspace and a BPMN Diagram Template', async () => {
303
+ await modelerHomePage.createDisposableWorkspace('AI Agent Import Workspace ' + processName);
304
+ await modelerHomePage.clickDiagramTypeDropdown();
305
+ await modelerHomePage.clickBpmnTemplateOption();
306
+ });
307
+ const taskElementId = await test.step('Append a Task and Open the Change Element Popup', async () => {
308
+ await (0, test_1.expect)(modelerCreatePage.generalPanel).toBeVisible({
309
+ timeout: 120000,
310
+ });
311
+ // Only the diagram name is set. The sibling flows also set the process
312
+ // ID here, but this test does not need it, and typing it before the
313
+ // canvas click has deselected the start event puts the ID on the start
314
+ // event: renaming StartEvent_1 drops its append indicator and the create
315
+ // pad never opens.
316
+ await modelerCreatePage.enterDiagramName(processName);
317
+ const elementId = await modelerCreatePage.appendTaskToStartEvent();
318
+ // The AI Agent templates require engines.camunda ^8.10; on a lower
319
+ // diagram version they land in "Requires newer Camunda version".
320
+ await modelerCreatePage.setEngineVersion('8.10');
321
+ await modelerCreatePage.openChangeElementPopupForElement(elementId);
322
+ return elementId;
323
+ });
324
+ await test.step('Import the AI Agent Connector from the Marketplace', async () => {
325
+ await modelerCreatePage.clickMarketPlaceButton();
326
+ await connectorMarketplacePage.clickSearchForConnectorTextbox();
327
+ await connectorMarketplacePage.fillSearchForConnectorTextbox('AI Agent');
328
+ await connectorMarketplacePage.waitForConnectorSearchResults();
329
+ const imported = await connectorMarketplacePage.importConnectorAndAssertNoImportError();
330
+ (0, test_1.expect)(imported, 'A fresh Workspace must exercise the AI Agent Marketplace import write.').toBe(true);
331
+ });
332
+ return taskElementId;
333
+ };
334
+ test('AI Agent Marketplace Import User Flow', async ({ modelerHomePage, modelerCreatePage, connectorMarketplacePage, }) => {
335
+ test.slow();
336
+ const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
337
+ const taskElementId = await importAiAgentFromMarketplace({ modelerHomePage, modelerCreatePage, connectorMarketplacePage }, 'AI_Agent_Marketplace_Import_Process' + randomString);
338
+ await test.step('Assert the AI Agent Task Template Is Available', async () => {
339
+ await modelerCreatePage.assertChangeElementTemplateListed(modelerCreatePage.aiAgentTaskConnectorOption, 'AI Agent', taskElementId);
340
+ });
341
+ });
342
+ test('AI Agent Sub-process Marketplace Import User Flow', async ({ modelerHomePage, modelerCreatePage, connectorMarketplacePage, }) => {
343
+ test.slow();
344
+ const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
345
+ const taskElementId = await importAiAgentFromMarketplace({ modelerHomePage, modelerCreatePage, connectorMarketplacePage }, 'AI_Agent_SubProcess_Import_Process' + randomString);
346
+ await test.step('Turn the Task into an Ad-hoc Sub-process', async () => {
347
+ await modelerCreatePage.changeElementToAdHocSubProcess(taskElementId);
348
+ });
349
+ await test.step('Assert the AI Agent Sub-process Template Is Available and Applies', async () => {
350
+ await modelerCreatePage.assertChangeElementTemplateListed(modelerCreatePage.aiAgentSubProcessConnectorOption, 'AI Agent', taskElementId);
351
+ await modelerCreatePage.applyAiAgentSubProcessTemplate();
352
+ });
353
+ });
283
354
  //Skipped due to bug 1252: https://github.com/camunda/c8-cross-component-e2e-tests/issues/1252
284
- SM_8_10_1.test.skip('Connectors Timer Event User Flow', async ({ page, modelerHomePage, modelerCreatePage, connectorMarketplacePage, connectorTemplatePage, navigationPage, operateProcessInstancePage, operateHomePage, operateProcessesPage, }) => {
285
- SM_8_10_1.test.slow();
355
+ test.skip('Connectors Timer Event User Flow', async ({ page, modelerHomePage, modelerCreatePage, connectorMarketplacePage, connectorTemplatePage, navigationPage, operateProcessInstancePage, operateHomePage, operateProcessesPage, }) => {
356
+ test.slow();
286
357
  const processName = 'Connectors_Timer_Event_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
287
- await SM_8_10_1.test.step('Open Cross Component Test Project and Create a REST Connector Document Handling Flow', async () => {
358
+ await test.step('Open Cross Component Test Project and Create a REST Connector Document Handling Flow', async () => {
288
359
  await (0, UtilitiesPage_1.modelAndRunConnectorsTimerEventDiagram)(modelerCreatePage, modelerHomePage, processName, page, connectorMarketplacePage, connectorTemplatePage, mtTenant);
289
360
  });
290
- await SM_8_10_1.test.step('View Process Instance in Operate, assert it completes and assert variable values are correct', async () => {
361
+ await test.step('View Process Instance in Operate, assert it completes and assert variable values are correct', async () => {
291
362
  await navigationPage.goToOperate();
292
363
  await operateHomePage.clickProcessesTab();
293
364
  await operateProcessesPage.clickProcessInstanceLink(processName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1465",
3
+ "version": "0.0.1466",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",