@camunda/e2e-test-suite 0.0.794 → 0.0.796

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.
@@ -21,6 +21,7 @@ declare class ClusterPage {
21
21
  readonly connectorSecretsTab: Locator;
22
22
  readonly createAClusterButton: Locator;
23
23
  readonly clusterVersionOption: Locator;
24
+ readonly generationSearchInput: Locator;
24
25
  readonly g3DevClusterType: Locator;
25
26
  readonly alphaTab: Locator;
26
27
  readonly devTab: Locator;
@@ -27,6 +27,7 @@ class ClusterPage {
27
27
  connectorSecretsTab;
28
28
  createAClusterButton;
29
29
  clusterVersionOption;
30
+ generationSearchInput;
30
31
  g3DevClusterType;
31
32
  alphaTab;
32
33
  devTab;
@@ -87,6 +88,7 @@ class ClusterPage {
87
88
  name: 'Create a Cluster',
88
89
  });
89
90
  this.clusterVersionOption = page.getByText(`${process.env.CLUSTER_VERSION}`, { exact: true });
91
+ this.generationSearchInput = page.getByPlaceholder('Search generations');
90
92
  this.g3DevClusterType = page.locator('label').filter({
91
93
  hasText: this.defaultClusterType,
92
94
  });
@@ -305,14 +307,29 @@ class ClusterPage {
305
307
  this.clickStableTab,
306
308
  this.clickAlphaTab,
307
309
  ];
308
- for (const clickTab of tabs) {
309
- try {
310
- await clickTab.call(this);
311
- await this.clusterVersionOption.click({ timeout: 60000 });
312
- return;
313
- }
314
- catch (error) {
315
- console.error('Error clicking tab or cluster version option:', error);
310
+ // The channel tabs no longer list every generation — the list is filtered
311
+ // by a "Search generations" box, and switching tabs clears that box. So on
312
+ // each channel we must (re)type the generation name before its option
313
+ // renders. A generation created via the API moments earlier can also take
314
+ // time to surface, so cycle through every channel repeatedly within a
315
+ // bounded budget (maxCycles * tabs * perTabTimeout) rather than a single
316
+ // pass, giving a late-propagating generation multiple chances on any channel.
317
+ const maxCycles = 6;
318
+ const perTabTimeout = 12000;
319
+ for (let cycle = 0; cycle < maxCycles; cycle++) {
320
+ for (const clickTab of tabs) {
321
+ try {
322
+ await clickTab.call(this);
323
+ await (0, test_1.expect)(this.generationSearchInput).toBeVisible({
324
+ timeout: perTabTimeout,
325
+ });
326
+ await this.generationSearchInput.fill(`${process.env.CLUSTER_VERSION}`);
327
+ await this.clusterVersionOption.click({ timeout: perTabTimeout });
328
+ return;
329
+ }
330
+ catch (error) {
331
+ console.error(`Cycle ${cycle + 1}/${maxCycles}: cluster generation not found on this tab yet:`, error);
332
+ }
316
333
  }
317
334
  }
318
335
  throw new Error('Failed to find cluster generation');
@@ -13,5 +13,15 @@ declare class OperateProcessInstancePage {
13
13
  connectorResultVariableValue(variableName: string): Promise<Locator>;
14
14
  completedIconAssertion(): Promise<void>;
15
15
  activeIconAssertion(): Promise<void>;
16
+ getProcessVariableValue(variableName: string): Promise<Locator>;
17
+ assertProcessVariableContainsText(variableName: string, text: string): Promise<void>;
18
+ /**
19
+ * Gets the JSON value of a variable by name
20
+ */
21
+ getVariableJsonValue(variableName: string): Promise<unknown>;
22
+ /**
23
+ * Verifies that a variable exists and contains expected JSON content
24
+ */
25
+ verifyVariableJsonContent(variableName: string, assertions: (value: unknown) => void | Promise<void>): Promise<void>;
16
26
  }
17
27
  export { OperateProcessInstancePage };
@@ -72,5 +72,49 @@ class OperateProcessInstancePage {
72
72
  }
73
73
  throw new Error(`Active icon not visible after ${maxRetries} attempts.`);
74
74
  }
75
+ async getProcessVariableValue(variableName) {
76
+ return await this.page
77
+ .getByTestId(`variable-${variableName}`)
78
+ .getByRole('cell')
79
+ .nth(1);
80
+ }
81
+ async assertProcessVariableContainsText(variableName, text) {
82
+ const maxRetries = 3;
83
+ for (let retries = 0; retries < maxRetries; retries++) {
84
+ try {
85
+ const valueCell = await this.getProcessVariableValue(variableName);
86
+ await (0, test_1.expect)(valueCell).toContainText(text, { timeout: 30000 });
87
+ return;
88
+ }
89
+ catch (error) {
90
+ console.log(`Failed to assert variable ${variableName}: ${error}`);
91
+ await this.page.reload();
92
+ await (0, sleep_1.sleep)(10000);
93
+ }
94
+ }
95
+ throw new Error(`Failed to assert variable ${variableName} contains ${text} after ${maxRetries} attempts.`);
96
+ }
97
+ /**
98
+ * Gets the JSON value of a variable by name
99
+ */
100
+ async getVariableJsonValue(variableName) {
101
+ const variable = await this.getProcessVariableValue(variableName);
102
+ await (0, test_1.expect)(variable).toBeVisible();
103
+ const valueText = (await variable.textContent()) || '{}';
104
+ try {
105
+ return JSON.parse(valueText);
106
+ }
107
+ catch (error) {
108
+ throw new Error(`Failed to parse JSON for variable "${variableName}". ` +
109
+ `Raw value: ${valueText}. Original error: ${String(error)}`);
110
+ }
111
+ }
112
+ /**
113
+ * Verifies that a variable exists and contains expected JSON content
114
+ */
115
+ async verifyVariableJsonContent(variableName, assertions) {
116
+ const value = await this.getVariableJsonValue(variableName);
117
+ await assertions(value);
118
+ }
75
119
  }
76
120
  exports.OperateProcessInstancePage = OperateProcessInstancePage;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const c8Run_8_10_1 = require("../../fixtures/c8Run-8.10");
4
+ const test_1 = require("@playwright/test");
5
+ const UtilitiesPage_1 = require("../../pages/c8Run-8.10/UtilitiesPage");
6
+ const _setup_1 = require("../../test-setup.js");
7
+ const apiHelpers_1 = require("../../utils/apiHelpers");
8
+ const keycloakHelpers_1 = require("../../utils/keycloakHelpers");
9
+ const zeebeClient_1 = require("../../utils/zeebeClient");
10
+ const sleep_1 = require("../../utils/sleep");
11
+ let bearerToken;
12
+ const authMethods = [
13
+ {
14
+ name: 'Basic Auth',
15
+ processId: 'mcp_remote_client_basic_auth',
16
+ },
17
+ {
18
+ name: 'Bearer Token',
19
+ processId: 'mcp_remote_client_bearer_auth',
20
+ getVariables: () => ({ bearerToken }),
21
+ },
22
+ {
23
+ name: 'OAuth Client Credentials',
24
+ processId: 'mcp_remote_client_oauth_auth',
25
+ },
26
+ ];
27
+ c8Run_8_10_1.test.beforeAll(async () => {
28
+ if (process.env.MCP_SERVER_AVAILABLE !== 'true') {
29
+ return;
30
+ }
31
+ await (0, apiHelpers_1.validateMcpServerHealth)('http://127.0.0.1:12001'); // No auth
32
+ await (0, apiHelpers_1.validateMcpServerHealth)('http://127.0.0.1:12002'); // Basic auth
33
+ await (0, apiHelpers_1.validateMcpServerHealth)('http://127.0.0.1:12004'); // OAuth
34
+ await (0, apiHelpers_1.waitForConnectorsReady)();
35
+ await (0, keycloakHelpers_1.validateKeycloakHealth)();
36
+ bearerToken = await (0, keycloakHelpers_1.getAccessToken)();
37
+ await (0, zeebeClient_1.deploy)([
38
+ './resources/mcp_server/mcp_remote_client_basic_auth.bpmn',
39
+ './resources/mcp_server/mcp_remote_client_bearer_auth.bpmn',
40
+ './resources/mcp_server/mcp_remote_client_oauth_auth.bpmn',
41
+ ]);
42
+ });
43
+ c8Run_8_10_1.test.describe('@tasklistV2 MCP Client connector authentication tests', () => {
44
+ c8Run_8_10_1.test.skip(process.env.MCP_SERVER_AVAILABLE !== 'true', 'MCP authentication servers require Docker and are only available on Linux CI runners');
45
+ c8Run_8_10_1.test.beforeEach(async ({ page, operateLoginPage, operateHomePage }) => {
46
+ await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
47
+ await operateLoginPage.login('demo', 'demo');
48
+ await operateHomePage.operateBannerIsVisible();
49
+ });
50
+ c8Run_8_10_1.test.afterEach(async ({ page }, testInfo) => {
51
+ await (0, _setup_1.captureScreenshot)(page, testInfo);
52
+ await (0, _setup_1.captureFailureVideo)(page, testInfo);
53
+ });
54
+ for (const { name, processId, getVariables } of authMethods) {
55
+ (0, c8Run_8_10_1.test)(`As a user I can authenticate using ${name} and list tools`, async ({ operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
56
+ await (0, zeebeClient_1.createInstances)(processId, 1, 1, getVariables?.());
57
+ await (0, sleep_1.sleep)(2000);
58
+ await c8Run_8_10_1.test.step('Navigate to completed process instance', async () => {
59
+ await operateHomePage.clickProcessesTab();
60
+ await operateProcessesPage.clickProcessCompletedCheckbox();
61
+ await operateProcessesPage.clickProcessInstanceLink(processId);
62
+ await operateProcessInstancePage.completedIconAssertion();
63
+ });
64
+ await c8Run_8_10_1.test.step(`Verify ${name} successfully listed tools`, async () => {
65
+ await operateProcessInstancePage.verifyVariableJsonContent('listToolsResult', (value) => {
66
+ const data = value;
67
+ (0, test_1.expect)(data.toolDefinitions).toBeDefined();
68
+ (0, test_1.expect)(data.toolDefinitions.length).toBeGreaterThan(0);
69
+ (0, test_1.expect)(data.toolDefinitions.some((tool) => tool.name)).toBe(true);
70
+ });
71
+ });
72
+ });
73
+ }
74
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const c8Run_8_10_1 = require("../../fixtures/c8Run-8.10");
4
+ const test_1 = require("@playwright/test");
5
+ const UtilitiesPage_1 = require("../../pages/c8Run-8.10/UtilitiesPage");
6
+ const _setup_1 = require("../../test-setup.js");
7
+ const apiHelpers_1 = require("../../utils/apiHelpers");
8
+ const zeebeClient_1 = require("../../utils/zeebeClient");
9
+ const sleep_1 = require("../../utils/sleep");
10
+ c8Run_8_10_1.test.beforeAll(async () => {
11
+ if (process.env.MCP_SERVER_AVAILABLE !== 'true') {
12
+ return;
13
+ }
14
+ await (0, apiHelpers_1.validateMcpServerHealth)();
15
+ await (0, apiHelpers_1.waitForConnectorsReady)();
16
+ await (0, zeebeClient_1.deploy)(['./resources/mcp_server/mcp_remote_client_operations.bpmn']);
17
+ await (0, zeebeClient_1.createInstances)('mcp_remote_client', 1, 1);
18
+ await (0, sleep_1.sleep)(2000);
19
+ });
20
+ c8Run_8_10_1.test.describe('@tasklistV2 MCP Client connector tests', () => {
21
+ c8Run_8_10_1.test.skip(process.env.MCP_SERVER_AVAILABLE !== 'true', 'MCP server is only available on Linux and macOS CI runners');
22
+ c8Run_8_10_1.test.beforeEach(async ({ page, operateLoginPage, operateHomePage }) => {
23
+ await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
24
+ await operateLoginPage.login('demo', 'demo');
25
+ await operateHomePage.operateBannerIsVisible();
26
+ });
27
+ c8Run_8_10_1.test.afterEach(async ({ page }, testInfo) => {
28
+ await (0, _setup_1.captureScreenshot)(page, testInfo);
29
+ await (0, _setup_1.captureFailureVideo)(page, testInfo);
30
+ });
31
+ (0, c8Run_8_10_1.test)('As an user I can invoke the MCP Client connector and use all its operations', async ({ operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
32
+ c8Run_8_10_1.test.slow();
33
+ await c8Run_8_10_1.test.step('Navigate to completed process instance', async () => {
34
+ await operateHomePage.clickProcessesTab();
35
+ await operateProcessesPage.clickProcessCompletedCheckbox();
36
+ await operateProcessesPage.clickProcessInstanceLink('mcp_remote_client');
37
+ await operateProcessInstancePage.completedIconAssertion();
38
+ });
39
+ await c8Run_8_10_1.test.step('Verify listToolsResult variable contains tools', async () => {
40
+ await operateProcessInstancePage.verifyVariableJsonContent('listToolsResult', (value) => {
41
+ const data = value;
42
+ (0, test_1.expect)(data.toolDefinitions).toBeDefined();
43
+ (0, test_1.expect)(data.toolDefinitions.length).toBeGreaterThan(0);
44
+ const greetTool = data.toolDefinitions.find((tool) => tool.name === 'greet');
45
+ (0, test_1.expect)(greetTool).toBeDefined();
46
+ (0, test_1.expect)(greetTool?.description).toBe('Return a nice greeting message');
47
+ });
48
+ });
49
+ await c8Run_8_10_1.test.step('Verify callToolResult variable from greet tool call', async () => {
50
+ await operateProcessInstancePage.verifyVariableJsonContent('callToolResult', (value) => {
51
+ const data = value;
52
+ (0, test_1.expect)(data.name).toBe('greet');
53
+ (0, test_1.expect)(data.isError).toBe(false);
54
+ (0, test_1.expect)(data.content).toBeDefined();
55
+ (0, test_1.expect)(data.content[0].type).toBe('text');
56
+ (0, test_1.expect)(data.content[0].text).toContain('Hello, John');
57
+ });
58
+ });
59
+ await c8Run_8_10_1.test.step('Verify listResourcesResult variable contains resources', async () => {
60
+ await operateProcessInstancePage.verifyVariableJsonContent('listResourcesResult', (value) => {
61
+ const data = value;
62
+ (0, test_1.expect)(data.resources).toBeDefined();
63
+ (0, test_1.expect)(data.resources.length).toBeGreaterThan(0);
64
+ const jokesResource = data.resources.find((resource) => resource.uri === 'file://jokes-guide.md');
65
+ (0, test_1.expect)(jokesResource).toBeDefined();
66
+ (0, test_1.expect)(jokesResource?.name).toBe('Jokes Guideline');
67
+ (0, test_1.expect)(jokesResource?.mimeType).toBe('text/markdown');
68
+ });
69
+ });
70
+ await c8Run_8_10_1.test.step('Verify listResourcesTemplateResult variable contains resource templates', async () => {
71
+ await operateProcessInstancePage.verifyVariableJsonContent('listResourcesTemplateResult', (value) => {
72
+ const data = value;
73
+ (0, test_1.expect)(data.resourceTemplates).toBeDefined();
74
+ (0, test_1.expect)(data.resourceTemplates).toEqual([]);
75
+ });
76
+ });
77
+ await c8Run_8_10_1.test.step('Verify readResourceResult variable from jokes-guide.md', async () => {
78
+ await operateProcessInstancePage.verifyVariableJsonContent('readResourceResult', (value) => {
79
+ const data = value;
80
+ (0, test_1.expect)(data.contents).toBeDefined();
81
+ (0, test_1.expect)(data.contents.length).toBeGreaterThan(0);
82
+ (0, test_1.expect)(data.contents[0].uri).toBe('file://jokes-guide.md');
83
+ (0, test_1.expect)(data.contents[0].mimeType).toBe('text/markdown');
84
+ (0, test_1.expect)(data.contents[0].text).toContain('How to Write Jokes');
85
+ });
86
+ });
87
+ await c8Run_8_10_1.test.step('Verify listPromptsResult variable contains prompts', async () => {
88
+ await operateProcessInstancePage.verifyVariableJsonContent('listPromptsResult', (value) => {
89
+ const data = value;
90
+ (0, test_1.expect)(data.promptDescriptions).toBeDefined();
91
+ (0, test_1.expect)(data.promptDescriptions.length).toBeGreaterThan(0);
92
+ const greetingPrompt = data.promptDescriptions.find((prompt) => prompt.name === 'get-greeting');
93
+ (0, test_1.expect)(greetingPrompt).toBeDefined();
94
+ (0, test_1.expect)(greetingPrompt?.description).toContain('personalized greeting');
95
+ });
96
+ });
97
+ await c8Run_8_10_1.test.step('Verify getPromptResult variable from get-greeting prompt', async () => {
98
+ await operateProcessInstancePage.verifyVariableJsonContent('getPromptResult', (value) => {
99
+ const data = value;
100
+ (0, test_1.expect)(data.description).toBe('Greeting Prompt');
101
+ (0, test_1.expect)(data.messages).toBeDefined();
102
+ (0, test_1.expect)(data.messages.length).toBeGreaterThan(0);
103
+ (0, test_1.expect)(data.messages[0].role).toBe('user');
104
+ (0, test_1.expect)(data.messages[0].content.text).toContain('John');
105
+ (0, test_1.expect)(data.messages[0].content.text).toContain('greet tool');
106
+ });
107
+ });
108
+ });
109
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.794",
3
+ "version": "0.0.796",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",