@camunda/e2e-test-suite 0.0.728 → 0.0.730
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.10/ClusterDetailsPage.js +7 -3
- package/dist/pages/8.10/ConnectorMarketplacePage.d.ts +2 -0
- package/dist/pages/8.10/ConnectorMarketplacePage.js +31 -2
- package/dist/pages/8.10/ConnectorSettingsPage.d.ts +1 -0
- package/dist/pages/8.10/ConnectorSettingsPage.js +5 -0
- package/dist/pages/8.10/LoginPage.js +19 -8
- package/dist/pages/8.10/ModelerCreatePage.js +23 -9
- package/dist/pages/8.10/UtilitiesPage.js +24 -0
- package/package.json +1 -1
|
@@ -445,12 +445,16 @@ class ClusterDetailsPage {
|
|
|
445
445
|
async createAPIClient(name) {
|
|
446
446
|
const maxRetries = 3;
|
|
447
447
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
448
|
+
// Use a suffixed name on retries so duplicate-name validation never
|
|
449
|
+
// leaves the Create button permanently disabled. A prior attempt may
|
|
450
|
+
// have created the client before we could detect the credentials dialog.
|
|
451
|
+
const attemptName = attempt === 0 ? name : `${name}-r${attempt}`;
|
|
448
452
|
try {
|
|
449
453
|
await this.clickCreateClientButton();
|
|
450
454
|
await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible({
|
|
451
455
|
timeout: 30000,
|
|
452
456
|
});
|
|
453
|
-
await this.fillAPIClientName(
|
|
457
|
+
await this.fillAPIClientName(attemptName);
|
|
454
458
|
await this.checkOrchestrationClusterCheckbox();
|
|
455
459
|
await this.checkOptimizeCheckbox();
|
|
456
460
|
await this.checkSecretsCheckbox();
|
|
@@ -487,7 +491,7 @@ class ClusterDetailsPage {
|
|
|
487
491
|
.getByText('The Client Secret will not be shown again.')
|
|
488
492
|
.first()).toBeVisible();
|
|
489
493
|
try {
|
|
490
|
-
await (0, test_1.expect)(this.clientsList.filter({ hasText:
|
|
494
|
+
await (0, test_1.expect)(this.clientsList.filter({ hasText: attemptName })).toContainText(/(?=.*Orchestration)(?=.*Optimize)(?=.*Secrets)/, {
|
|
491
495
|
timeout: 10000,
|
|
492
496
|
});
|
|
493
497
|
}
|
|
@@ -502,7 +506,7 @@ class ClusterDetailsPage {
|
|
|
502
506
|
.catch(() => false);
|
|
503
507
|
if (!oopsVisible)
|
|
504
508
|
throw err;
|
|
505
|
-
console.warn(`createAPIClient: clients list panel is in Oops state; skipping row scope assertion for "${
|
|
509
|
+
console.warn(`createAPIClient: clients list panel is in Oops state; skipping row scope assertion for "${attemptName}".`);
|
|
506
510
|
}
|
|
507
511
|
return;
|
|
508
512
|
}
|
|
@@ -5,7 +5,9 @@ declare class ConnectorMarketplacePage {
|
|
|
5
5
|
readonly downloadToProjectButton: Locator;
|
|
6
6
|
readonly snackbar: Locator;
|
|
7
7
|
readonly closeButton: Locator;
|
|
8
|
+
readonly addToProjectButton: Locator;
|
|
8
9
|
readonly replaceResourceButton: Locator;
|
|
10
|
+
readonly saveAsCopyButton: Locator;
|
|
9
11
|
readonly cancelButton: Locator;
|
|
10
12
|
constructor(page: Page);
|
|
11
13
|
clickSearchForConnectorTextbox(): Promise<void>;
|
|
@@ -7,7 +7,9 @@ class ConnectorMarketplacePage {
|
|
|
7
7
|
downloadToProjectButton;
|
|
8
8
|
snackbar;
|
|
9
9
|
closeButton;
|
|
10
|
+
addToProjectButton;
|
|
10
11
|
replaceResourceButton;
|
|
12
|
+
saveAsCopyButton;
|
|
11
13
|
cancelButton;
|
|
12
14
|
constructor(page) {
|
|
13
15
|
this.page = page;
|
|
@@ -17,9 +19,13 @@ class ConnectorMarketplacePage {
|
|
|
17
19
|
.first();
|
|
18
20
|
this.snackbar = page.locator('[data-test="snackbar"]');
|
|
19
21
|
this.closeButton = page.getByRole('button', { name: 'Close' });
|
|
22
|
+
this.addToProjectButton = page.getByRole('button', {
|
|
23
|
+
name: 'Add to project',
|
|
24
|
+
});
|
|
20
25
|
this.replaceResourceButton = page.getByRole('button', {
|
|
21
26
|
name: 'Replace resource',
|
|
22
27
|
});
|
|
28
|
+
this.saveAsCopyButton = page.getByRole('button', { name: 'Save as copy' });
|
|
23
29
|
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
|
|
24
30
|
}
|
|
25
31
|
async clickSearchForConnectorTextbox() {
|
|
@@ -42,14 +48,37 @@ class ConnectorMarketplacePage {
|
|
|
42
48
|
}
|
|
43
49
|
async downloadConnectorToProject() {
|
|
44
50
|
await this.clickDownloadToProjectButton();
|
|
51
|
+
// After clicking "Download to project", Hub opens an ImportModal whose
|
|
52
|
+
// primary button depends on project state: "Add to project" (new template),
|
|
53
|
+
// "Replace resource" (same template exists), or "Save as copy" (conflict).
|
|
54
|
+
// Race all three click calls — Playwright's click() waits for actionability,
|
|
55
|
+
// so whichever button Hub actually renders will win the race and be clicked.
|
|
56
|
+
// If none appear within 20 s (e.g. the modal never opened), the race rejects
|
|
57
|
+
// and the catch path handles any residual Cancel button.
|
|
58
|
+
let imported = false;
|
|
45
59
|
try {
|
|
46
60
|
await Promise.race([
|
|
61
|
+
this.addToProjectButton.click({ timeout: 20000 }),
|
|
47
62
|
this.replaceResourceButton.click({ timeout: 20000 }),
|
|
48
|
-
this.
|
|
63
|
+
this.saveAsCopyButton.click({ timeout: 20000 }),
|
|
49
64
|
]);
|
|
65
|
+
imported = true;
|
|
50
66
|
}
|
|
51
67
|
catch (e) {
|
|
52
|
-
|
|
68
|
+
if (await this.cancelButton.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
69
|
+
await this.cancelButton.click({ timeout: 10000 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Wait for the snackbar that confirms the template was received, then close
|
|
73
|
+
// the marketplace dialog so the change-element popup is unblocked before the
|
|
74
|
+
// caller selects the newly installed connector.
|
|
75
|
+
if (imported) {
|
|
76
|
+
await this.snackbar
|
|
77
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
78
|
+
.catch(() => { });
|
|
79
|
+
if (await this.closeButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
80
|
+
await this.closeButton.click({ timeout: 10000 });
|
|
81
|
+
}
|
|
53
82
|
}
|
|
54
83
|
}
|
|
55
84
|
}
|
|
@@ -14,6 +14,7 @@ declare class ConnectorSettingsPage {
|
|
|
14
14
|
readonly httpEndpointTab: Locator;
|
|
15
15
|
readonly correlationTab: Locator;
|
|
16
16
|
readonly inputTab: Locator;
|
|
17
|
+
readonly detailsPanelToggle: Locator;
|
|
17
18
|
constructor(page: Page);
|
|
18
19
|
clickUrlInput(): Promise<void>;
|
|
19
20
|
fillUrlInput(url: string): Promise<void>;
|
|
@@ -18,6 +18,7 @@ class ConnectorSettingsPage {
|
|
|
18
18
|
httpEndpointTab;
|
|
19
19
|
correlationTab;
|
|
20
20
|
inputTab;
|
|
21
|
+
detailsPanelToggle;
|
|
21
22
|
constructor(page) {
|
|
22
23
|
this.page = page;
|
|
23
24
|
this.urlInput = page.getByLabel('URL');
|
|
@@ -50,6 +51,7 @@ class ConnectorSettingsPage {
|
|
|
50
51
|
.locator('div')
|
|
51
52
|
.filter({ hasText: /^Input$/ })
|
|
52
53
|
.first();
|
|
54
|
+
this.detailsPanelToggle = page.locator('[data-test="details-panel-toggle"]');
|
|
53
55
|
}
|
|
54
56
|
async clickUrlInput() {
|
|
55
57
|
await this.urlInput.click({ timeout: 90000 });
|
|
@@ -139,6 +141,9 @@ class ConnectorSettingsPage {
|
|
|
139
141
|
await this.correlationTab.click({ timeout: 60000 });
|
|
140
142
|
}
|
|
141
143
|
async clickInputTab() {
|
|
144
|
+
if (!(await this.inputTab.isVisible({ timeout: 5000 }).catch(() => false))) {
|
|
145
|
+
await this.detailsPanelToggle.click({ timeout: 10000 });
|
|
146
|
+
}
|
|
142
147
|
await this.inputTab.click({ timeout: 60000 });
|
|
143
148
|
}
|
|
144
149
|
}
|
|
@@ -70,14 +70,25 @@ class LoginPage {
|
|
|
70
70
|
}
|
|
71
71
|
async loginWithTestUser(credentials = {}) {
|
|
72
72
|
const { username = process.env.C8_USERNAME_TEST, password = process.env.C8_PASSWORD_TEST, } = credentials;
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
await this.
|
|
78
|
-
|
|
79
|
-
.
|
|
80
|
-
|
|
73
|
+
// Skip navigation if the auth0 login page is already loaded — re-navigating
|
|
74
|
+
// to '/' when already on auth0 undoes the redirect and causes a 60-second
|
|
75
|
+
// waitForURL race. This is the common case when called from loginWithRetry,
|
|
76
|
+
// which already lands on the auth0 page before calling this method.
|
|
77
|
+
const alreadyOnLoginPage = await this.usernameInput.locator
|
|
78
|
+
.isVisible({ timeout: 2000 })
|
|
79
|
+
.catch(() => false);
|
|
80
|
+
if (!alreadyOnLoginPage) {
|
|
81
|
+
// Navigate to app root to trigger auth redirect. The Console app (at
|
|
82
|
+
// console.* hostnames) embeds a hidden invite-user email input also labeled
|
|
83
|
+
// "Email address" that would otherwise satisfy the locator below before the
|
|
84
|
+
// auth0 redirect has had a chance to complete.
|
|
85
|
+
await this.page.goto('/');
|
|
86
|
+
await this.page
|
|
87
|
+
.waitForURL((url) => !url.hostname.includes('console'), {
|
|
88
|
+
timeout: 60000,
|
|
89
|
+
})
|
|
90
|
+
.catch(() => { });
|
|
91
|
+
}
|
|
81
92
|
const onPasswordStep = (await this.passwordHeading
|
|
82
93
|
.isVisible({ timeout: 5000 })
|
|
83
94
|
.catch(() => false)) ||
|
|
@@ -188,7 +188,8 @@ class ModelerCreatePage {
|
|
|
188
188
|
this.marketPlaceButton = page.getByTitle('Browse Marketplace for more Connectors');
|
|
189
189
|
this.publicHolidayConnectorOption = page
|
|
190
190
|
.locator('[data-test="modeler"]')
|
|
191
|
-
.getByText('Worldwide Public Holiday')
|
|
191
|
+
.getByText('Worldwide Public Holiday')
|
|
192
|
+
.first();
|
|
192
193
|
this.publicHolidayYearOption = page.getByLabel('Year');
|
|
193
194
|
this.publicHolidayCountryCodeOption = page.getByLabel('Countrycode');
|
|
194
195
|
this.implementationSection = page.locator('[data-group-id="group-userTaskImplementation"]');
|
|
@@ -773,27 +774,40 @@ class ModelerCreatePage {
|
|
|
773
774
|
const maxRetries = 4;
|
|
774
775
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
775
776
|
try {
|
|
776
|
-
if (retries
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
else {
|
|
777
|
+
if (retries === 2) {
|
|
778
|
+
// Re-download connector on second retry in case the first download failed
|
|
780
779
|
await this.clickMarketPlaceButton();
|
|
781
780
|
const connectorMarketplacePage = new ConnectorMarketplacePage_1.ConnectorMarketplacePage(this.page);
|
|
782
781
|
await connectorMarketplacePage.clickSearchForConnectorTextbox();
|
|
783
782
|
await connectorMarketplacePage.fillSearchForConnectorTextbox('Public Holiday Connector');
|
|
784
783
|
await (0, sleep_1.sleep)(10000);
|
|
785
784
|
await connectorMarketplacePage.downloadConnectorToProject();
|
|
786
|
-
await this.publicHolidayConnectorOption.click({ timeout: 120000 });
|
|
787
785
|
}
|
|
786
|
+
// Always open the change-type popup before trying to click the option
|
|
787
|
+
await this.clickCanvas();
|
|
788
|
+
await this.secondElement.click({ timeout: 60000 });
|
|
789
|
+
await this.clickChangeTypeButton();
|
|
790
|
+
await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
|
|
791
|
+
// Filter the virtualized change-element popup so the option scrolls into view
|
|
792
|
+
const changeElementSearch = this.page.locator('.djs-popup-search input');
|
|
793
|
+
if (await changeElementSearch
|
|
794
|
+
.isVisible({ timeout: 5000 })
|
|
795
|
+
.catch(() => false)) {
|
|
796
|
+
await changeElementSearch.pressSequentially('Worldwide Public Holiday', { delay: 50 });
|
|
797
|
+
}
|
|
798
|
+
await this.publicHolidayConnectorOption.waitFor({
|
|
799
|
+
state: 'visible',
|
|
800
|
+
timeout: 60000,
|
|
801
|
+
});
|
|
802
|
+
await this.publicHolidayConnectorOption.click({ timeout: 30000 });
|
|
788
803
|
return;
|
|
789
804
|
}
|
|
790
805
|
catch (error) {
|
|
791
806
|
console.error(`Click attempt ${retries + 1} failed: ${error}`);
|
|
807
|
+
if (retries >= maxRetries - 1)
|
|
808
|
+
break;
|
|
792
809
|
await this.page.reload();
|
|
793
810
|
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
794
|
-
await this.clickCanvas();
|
|
795
|
-
await this.secondElement.click({ timeout: 60000 });
|
|
796
|
-
await this.clickChangeTypeButton();
|
|
797
811
|
}
|
|
798
812
|
}
|
|
799
813
|
throw new Error(`Failed to click the button after ${maxRetries} attempts.`);
|
|
@@ -31,6 +31,30 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
|
|
|
31
31
|
})
|
|
32
32
|
.catch(() => { });
|
|
33
33
|
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
34
|
+
// The evaluate above ran on about:blank, not on the Console domain, so it
|
|
35
|
+
// did not clear Console's own localStorage. Clear it now while on the
|
|
36
|
+
// Console domain, then reload to force a fresh auth check with no cached
|
|
37
|
+
// tokens — without this, Console can keep a session alive via localStorage
|
|
38
|
+
// even after cookies are cleared, preventing the auth0 redirect.
|
|
39
|
+
await page
|
|
40
|
+
.evaluate(() => {
|
|
41
|
+
try {
|
|
42
|
+
localStorage.clear();
|
|
43
|
+
}
|
|
44
|
+
catch (_) {
|
|
45
|
+
// ignore — storage may be unavailable in some contexts
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
sessionStorage.clear();
|
|
49
|
+
}
|
|
50
|
+
catch (_) {
|
|
51
|
+
// ignore — storage may be unavailable in some contexts
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.catch(() => { });
|
|
55
|
+
await page
|
|
56
|
+
.reload({ waitUntil: 'domcontentloaded', timeout: 60000 })
|
|
57
|
+
.catch(() => { });
|
|
34
58
|
await page
|
|
35
59
|
.waitForLoadState('networkidle', { timeout: 30000 })
|
|
36
60
|
.catch(() => { });
|