@camunda/e2e-test-suite 0.0.1248 → 0.0.1250
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.7/AppsPage.d.ts +7 -3
- package/dist/pages/8.7/AppsPage.js +187 -3
- package/dist/pages/8.7/ClusterPage.js +22 -2
- package/dist/pages/8.7/ClusterSecretsPage.js +22 -1
- package/dist/pages/8.7/HomePage.d.ts +2 -0
- package/dist/pages/8.7/HomePage.js +70 -6
- package/dist/pages/8.7/LoginPage.js +15 -0
- package/dist/pages/8.7/ModelerCreatePage.d.ts +4 -0
- package/dist/pages/8.7/ModelerCreatePage.js +59 -5
- package/dist/pages/8.7/ModelerHomePage.d.ts +11 -0
- package/dist/pages/8.7/ModelerHomePage.js +184 -10
- package/dist/pages/8.7/ProcessApplicationPage.js +27 -7
- package/dist/pages/8.7/UtilitiesPage.js +54 -1
- package/dist/pages/8.9/AppsPage.d.ts +2 -0
- package/dist/pages/8.9/AppsPage.js +114 -0
- package/dist/pages/8.9/ClusterDetailsPage.d.ts +3 -0
- package/dist/pages/8.9/ClusterDetailsPage.js +92 -25
- package/dist/pages/8.9/ClusterPage.js +49 -8
- package/dist/pages/8.9/ClusterSecretsPage.js +47 -2
- package/dist/pages/8.9/HomePage.d.ts +3 -0
- package/dist/pages/8.9/HomePage.js +44 -5
- package/dist/pages/8.9/LoginPage.d.ts +1 -0
- package/dist/pages/8.9/LoginPage.js +19 -0
- package/dist/pages/8.9/ModelerHomePage.d.ts +7 -0
- package/dist/pages/8.9/ModelerHomePage.js +122 -3
- package/dist/pages/8.9/UtilitiesPage.js +60 -2
- package/dist/tests/8.7/test-setup.spec.js +18 -5
- package/dist/tests/8.7/web-modeler-user-flows.spec.js +14 -14
- package/dist/tests/8.9/test-setup.spec.js +11 -4
- package/dist/utils/authErrorClassification.d.ts +5 -0
- package/dist/utils/authErrorClassification.js +49 -1
- package/dist/utils/consoleApiHelpers.d.ts +1 -0
- package/dist/utils/consoleApiHelpers.js +183 -30
- package/dist/utils/resetSession.js +7 -0
- package/package.json +1 -1
|
@@ -9,6 +9,11 @@ const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
|
9
9
|
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
10
10
|
class ModelerHomePage {
|
|
11
11
|
defaultFolderName = 'Cross Component Test Project';
|
|
12
|
+
// The default cluster test-setup.spec.ts's "Create Default Cluster" test
|
|
13
|
+
// provisions for the whole suite -- assigned to the shared workspace's
|
|
14
|
+
// Environments step so downstream specs can deploy to it without a manual
|
|
15
|
+
// assignment step first.
|
|
16
|
+
defaultClusterName = 'Test Cluster';
|
|
12
17
|
page;
|
|
13
18
|
// Since camunda/camunda-hub#25831 files and folders cannot sit at a
|
|
14
19
|
// workspace ("project" in today's UI) root -- only process applications and
|
|
@@ -25,6 +30,11 @@ class ModelerHomePage {
|
|
|
25
30
|
processApplicationName;
|
|
26
31
|
modelerPageBanner;
|
|
27
32
|
createNewProjectButton;
|
|
33
|
+
createWorkspaceButton;
|
|
34
|
+
workspaceWizardNameInput;
|
|
35
|
+
workspaceWizardSubmitButton;
|
|
36
|
+
workspaceWizardMembersStep;
|
|
37
|
+
workspaceWizardEnvironmentsHeading;
|
|
28
38
|
projectNameInput;
|
|
29
39
|
formNameInput;
|
|
30
40
|
chooseBpmnTemplateButton;
|
|
@@ -53,9 +63,24 @@ class ModelerHomePage {
|
|
|
53
63
|
constructor(page) {
|
|
54
64
|
this.page = page;
|
|
55
65
|
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
56
|
-
|
|
66
|
+
// Hub's design-system shell renders the app header as a role=banner with no
|
|
67
|
+
// accessible name after the Camunda Hub rebrand, so the classic
|
|
68
|
+
// name-matched banner no longer resolves; the Modeler logo link inside it
|
|
69
|
+
// carries "Camunda Hub home" when the org has workspacesEnabled, else
|
|
70
|
+
// "Camunda Modeler home" (camunda-hub NavShellDs.common.tsx) -- match
|
|
71
|
+
// either. Keep the legacy named-banner branch for shells where the rename
|
|
72
|
+
// has not shipped yet.
|
|
73
|
+
this.modelerPageBanner = page
|
|
74
|
+
.getByRole('banner', {
|
|
57
75
|
name: 'Camunda Modeler',
|
|
58
|
-
})
|
|
76
|
+
})
|
|
77
|
+
.or(page
|
|
78
|
+
.getByRole('link', { name: 'Camunda Hub home' })
|
|
79
|
+
.and(page.locator(':not([href$="/login"])')))
|
|
80
|
+
.or(page
|
|
81
|
+
.getByRole('link', { name: 'Camunda Modeler home' })
|
|
82
|
+
.and(page.locator(':not([href$="/login"])')))
|
|
83
|
+
.first();
|
|
59
84
|
// Hub renamed this button to "Create project" on both the classic and the
|
|
60
85
|
// design-system shell. The legacy label is kept so the flow still works
|
|
61
86
|
// against Modeler builds where the rename has not shipped yet. exact:true
|
|
@@ -68,6 +93,24 @@ class ModelerHomePage {
|
|
|
68
93
|
name: 'Create project',
|
|
69
94
|
exact: true,
|
|
70
95
|
}));
|
|
96
|
+
// Camunda Hub, which replaced Console + Web Modeler on SaaS, tops the
|
|
97
|
+
// hierarchy with a Workspaces overview whose "Create workspace" CTA opens a
|
|
98
|
+
// two-step wizard (General -> Members) that names the workspace up front.
|
|
99
|
+
// Same hooks as pages/SM-8.10/ModelerHomePage.ts. exact:true keeps the
|
|
100
|
+
// split button's options chevron out of the match.
|
|
101
|
+
this.createWorkspaceButton = page.getByRole('button', {
|
|
102
|
+
name: 'Create workspace',
|
|
103
|
+
exact: true,
|
|
104
|
+
});
|
|
105
|
+
this.workspaceWizardNameInput = page.locator('#workspace-name');
|
|
106
|
+
this.workspaceWizardSubmitButton = page.locator('[data-test="workspace-wizard-submit"]');
|
|
107
|
+
this.workspaceWizardMembersStep = page.locator('[data-test="step-members"]');
|
|
108
|
+
// When the org has environments enabled, an "Assign environments" step is
|
|
109
|
+
// inserted after Members -- camunda-hub's own
|
|
110
|
+
// e2e-playwright/pages/CreateWorkspacePage.ts detects it the same way.
|
|
111
|
+
this.workspaceWizardEnvironmentsHeading = page.getByRole('heading', {
|
|
112
|
+
name: 'Assign environments',
|
|
113
|
+
});
|
|
71
114
|
this.projectNameInput = page.locator('[data-test="editable-input"]');
|
|
72
115
|
this.formNameInput = page.locator('[data-test="editable-input"]');
|
|
73
116
|
this.idpApplicationNameInput = page.getByPlaceholder('New IDP Application');
|
|
@@ -105,8 +148,11 @@ class ModelerHomePage {
|
|
|
105
148
|
this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
|
|
106
149
|
this.openOrganizationsButton = page.getByLabel('Open Organizations');
|
|
107
150
|
this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
|
|
151
|
+
// On the Camunda Hub Workspaces overview the folder is a workspace tile
|
|
152
|
+
// whose "Open workspace <name>" button navigates into it.
|
|
108
153
|
this.crossComponentProjectFolder = page
|
|
109
|
-
.
|
|
154
|
+
.getByRole('button', { name: `Open workspace ${this.defaultFolderName}` })
|
|
155
|
+
.or(page.getByTitle(this.defaultFolderName, { exact: true }))
|
|
110
156
|
.first();
|
|
111
157
|
this.rows = page.getByRole('row');
|
|
112
158
|
this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
|
|
@@ -444,7 +490,76 @@ class ModelerHomePage {
|
|
|
444
490
|
.then(() => true)
|
|
445
491
|
.catch(() => false);
|
|
446
492
|
}
|
|
493
|
+
// Drives Camunda Hub's workspace-creation wizard. The name is entered on
|
|
494
|
+
// the General step, which enables "Next"; the same submit button then
|
|
495
|
+
// relabels itself through the remaining steps and finally reads "Create
|
|
496
|
+
// workspace" once it creates the workspace and routes the creator into its
|
|
497
|
+
// projects listing. Gate the Members-step click on that step becoming
|
|
498
|
+
// active so the shared submit button is not clicked twice while still on
|
|
499
|
+
// General.
|
|
500
|
+
async createWorkspaceViaWizard(name) {
|
|
501
|
+
await this.dismissOverlays();
|
|
502
|
+
await this.createWorkspaceButton.click({ timeout: 60000 });
|
|
503
|
+
await this.workspaceWizardNameInput.fill(name, { timeout: 60000 });
|
|
504
|
+
await this.workspaceWizardSubmitButton.click({ timeout: 60000 });
|
|
505
|
+
await (0, test_1.expect)(this.workspaceWizardMembersStep).toHaveAttribute('data-state', 'active', { timeout: 30000 });
|
|
506
|
+
await this.workspaceWizardSubmitButton.click({ timeout: 60000 });
|
|
507
|
+
// When the org has environments enabled, this only advances the wizard
|
|
508
|
+
// into an "Assign environments" step instead of submitting. Assign the
|
|
509
|
+
// suite's default cluster here rather than submitting with an empty
|
|
510
|
+
// selection -- otherwise this shared workspace starts with no
|
|
511
|
+
// environments and every downstream spec that deploys to it needs its
|
|
512
|
+
// own manual assignment first. Each card's accessible name is
|
|
513
|
+
// "Select <cluster name>" before it is picked (camunda-hub's own
|
|
514
|
+
// create-workspace-environments.spec.tsx asserts the same pattern).
|
|
515
|
+
// Sampled immediately (isVisible() doesn't wait) since the step change
|
|
516
|
+
// is a synchronous client-side re-render.
|
|
517
|
+
if (await this.workspaceWizardEnvironmentsHeading
|
|
518
|
+
.isVisible()
|
|
519
|
+
.catch(() => false)) {
|
|
520
|
+
// "Create Default Cluster" runs in parallel with this flow
|
|
521
|
+
// (test-setup.spec.ts's mode: 'parallel'), so the cluster this picker
|
|
522
|
+
// lists may still be provisioning -- wait for it a generous 90s rather
|
|
523
|
+
// than sampling once. Hub itself treats submitting unassigned as a
|
|
524
|
+
// supported, recoverable path for a real user ("Could not load
|
|
525
|
+
// environments. You can finish creating the workspace and assign them
|
|
526
|
+
// later."), but this shared workspace is relied on by every downstream
|
|
527
|
+
// spec that deploys to it -- silently submitting without the default
|
|
528
|
+
// environment turns a real setup problem here into confusing failures
|
|
529
|
+
// scattered across unrelated specs later. Fail loudly here instead.
|
|
530
|
+
const clusterOption = this.page.getByRole('button', {
|
|
531
|
+
name: new RegExp(`^Select ${this.defaultClusterName}`),
|
|
532
|
+
});
|
|
533
|
+
if (await this.becameVisible(clusterOption, 90000)) {
|
|
534
|
+
await clusterOption.click({ timeout: 10000 });
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
throw new Error(`"${this.defaultClusterName}" was not selectable on the Environments step within 90s -- refusing to create the shared workspace without its default environment assigned, since every downstream spec that deploys to it depends on that assignment.`);
|
|
538
|
+
}
|
|
539
|
+
await this.workspaceWizardSubmitButton.click({ timeout: 60000 });
|
|
540
|
+
}
|
|
541
|
+
await this.page.waitForURL(/\/workspaces?\/[^/]+\/projects/, {
|
|
542
|
+
timeout: 120000,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
447
545
|
async clickHomeBreadcrumb() {
|
|
546
|
+
// Camunda Hub has no Modeler home breadcrumb: its home is the Workspaces
|
|
547
|
+
// overview, reached through the header logo link so the SPA keeps its
|
|
548
|
+
// in-memory session (a full navigation re-runs the whole Auth0 hand-off).
|
|
549
|
+
// The link's accessible name is "Camunda Hub home" when the org has
|
|
550
|
+
// workspacesEnabled, else "Camunda Modeler home" (camunda-hub
|
|
551
|
+
// NavShellDs.common.tsx) -- match either.
|
|
552
|
+
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
553
|
+
await this.page
|
|
554
|
+
.locator('header[data-slot="app-header"]')
|
|
555
|
+
.getByRole('link', { name: 'Camunda Hub home' })
|
|
556
|
+
.or(this.page
|
|
557
|
+
.locator('header[data-slot="app-header"]')
|
|
558
|
+
.getByRole('link', { name: 'Camunda Modeler home' }))
|
|
559
|
+
.first()
|
|
560
|
+
.click({ timeout: 30000 });
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
448
563
|
try {
|
|
449
564
|
await this.homeBreadcrumb.click();
|
|
450
565
|
}
|
|
@@ -537,6 +652,10 @@ class ModelerHomePage {
|
|
|
537
652
|
return;
|
|
538
653
|
}
|
|
539
654
|
try {
|
|
655
|
+
if (await this.becameVisible(this.createWorkspaceButton, 5000)) {
|
|
656
|
+
await this.createWorkspaceViaWizard(this.defaultFolderName);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
540
659
|
await this.clickCreateNewProjectButton();
|
|
541
660
|
await this.enterNewProjectName(this.defaultFolderName);
|
|
542
661
|
return;
|
|
@@ -9,11 +9,34 @@ const sleep_1 = require("../../utils/sleep");
|
|
|
9
9
|
const randomSleep_1 = require("../../utils/randomSleep");
|
|
10
10
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
11
11
|
const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
|
|
12
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
12
13
|
// Tracks which worker-derived `timeout` values have already paid the
|
|
13
14
|
// stagger sleep below, so a worker only pays it once per process lifetime
|
|
14
15
|
// rather than on every call -- see the comment at the call site.
|
|
15
16
|
const staggeredWorkerTimeouts = new Set();
|
|
17
|
+
const onboardingTourSuppressed = new WeakSet();
|
|
18
|
+
// Mirrors camunda-hub@452978f: pre-seed the onboarding tour's "seen" localStorage flag
|
|
19
|
+
// (SEEN_KEY_PREFIX in .../tour-dialog/use-onboarding-seen.ts) so it never auto-opens. The
|
|
20
|
+
// "Welcome to Camunda Hub" tour mounts app-wide -- Console included, not just Modeler -- and
|
|
21
|
+
// hides the post-login banner link from the accessibility tree until dismissed, which times
|
|
22
|
+
// out assertTestUsesCorrectOrganization's banner check. addInitScript runs before any page
|
|
23
|
+
// script on every navigation/reload, so there's no login-response race to lose; the flag is
|
|
24
|
+
// matched by prefix since the user id isn't known ahead of login.
|
|
25
|
+
async function suppressOnboardingTour(page) {
|
|
26
|
+
if (onboardingTourSuppressed.has(page)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
onboardingTourSuppressed.add(page);
|
|
30
|
+
await page.addInitScript((prefixPattern) => {
|
|
31
|
+
const pattern = new RegExp(prefixPattern);
|
|
32
|
+
const originalGetItem = Storage.prototype.getItem;
|
|
33
|
+
Storage.prototype.getItem = function (key) {
|
|
34
|
+
return pattern.test(key) ? 'true' : originalGetItem.call(this, key);
|
|
35
|
+
};
|
|
36
|
+
}, '^hub\\.onboarding\\..*\\.seen\\.');
|
|
37
|
+
}
|
|
16
38
|
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5) {
|
|
39
|
+
await suppressOnboardingTour(page);
|
|
17
40
|
let lastError;
|
|
18
41
|
// `timeout` is a stable per-worker identifier (derived from workerIndex),
|
|
19
42
|
// and a worker is a long-lived process across every test/repeat it runs, so
|
|
@@ -25,6 +48,11 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
25
48
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
26
49
|
try {
|
|
27
50
|
await page.context().clearCookies();
|
|
51
|
+
// The Hub bearer-token cache in consoleApiHelpers.ts is keyed by Page
|
|
52
|
+
// for the tab's whole lifetime; without clearing it here alongside
|
|
53
|
+
// cookies/storage, a flow that logs a different user into the same
|
|
54
|
+
// page could have getUserToken() return the PREVIOUS user's token.
|
|
55
|
+
(0, consoleApiHelpers_1.resetHubBearerToken)(page);
|
|
28
56
|
await page.goto('about:blank');
|
|
29
57
|
await page
|
|
30
58
|
.evaluate(() => {
|
|
@@ -61,6 +89,17 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
61
89
|
}
|
|
62
90
|
catch (error) {
|
|
63
91
|
lastError = error;
|
|
92
|
+
// A rejected password or a blocked account is a credential/
|
|
93
|
+
// provisioning-level failure, not a transient one: every attempt
|
|
94
|
+
// resubmits the same env-supplied credentials, so retrying just
|
|
95
|
+
// reproduces the same rejection up to maxRetries times (observed:
|
|
96
|
+
// ~6 minutes burned across 5 identical "Wrong email or password"
|
|
97
|
+
// cycles for one seeded user). Fail on the first rejection instead.
|
|
98
|
+
if (String(error).includes(authErrorClassification_1.AUTH0_INVALID_CREDENTIALS) ||
|
|
99
|
+
String(error).includes(authErrorClassification_1.AUTH0_ACCOUNT_BLOCKED)) {
|
|
100
|
+
console.error(lastError);
|
|
101
|
+
throw lastError;
|
|
102
|
+
}
|
|
64
103
|
if (attempt < maxRetries - 1) {
|
|
65
104
|
const rateLimited = String(error).includes(authErrorClassification_1.AUTH0_RATE_LIMIT);
|
|
66
105
|
console.warn(`Attempt ${attempt + 1} failed for logging in. Retrying with clean session...`, error);
|
|
@@ -368,8 +407,14 @@ async function assertTestUsesCorrectOrganization(page) {
|
|
|
368
407
|
const homePage = new HomePage_1.HomePage(page);
|
|
369
408
|
if (page.url().includes('?code=') || page.url().includes('&state=')) {
|
|
370
409
|
console.log('OAuth callback detected, waiting for redirect...');
|
|
410
|
+
// Camunda Hub (which replaced Console on SaaS) only exchanges the code
|
|
411
|
+
// once its SPA has booted, and on CI runners that boot alone was measured
|
|
412
|
+
// at ~45s past /login-callback -- a 30s wait aborted the exchange on every
|
|
413
|
+
// attempt. Ported from 8.10's already-verified fix (same run, confirmed
|
|
414
|
+
// via CI run 36003828752: "Create Project Folder for User 7" exhausted
|
|
415
|
+
// all 5 login retries on this exact 30s timeout).
|
|
371
416
|
await page.waitForURL((url) => !url.toString().includes('?code='), {
|
|
372
|
-
timeout:
|
|
417
|
+
timeout: 120000,
|
|
373
418
|
});
|
|
374
419
|
await (0, sleep_1.sleep)(5000);
|
|
375
420
|
}
|
|
@@ -379,7 +424,20 @@ async function assertTestUsesCorrectOrganization(page) {
|
|
|
379
424
|
await (0, test_1.expect)(homePage.modelerBanner).toBeVisible({ timeout: 30000 });
|
|
380
425
|
}
|
|
381
426
|
else {
|
|
382
|
-
await (0, test_1.expect)(homePage.consoleBanner).toBeVisible({
|
|
427
|
+
await (0, test_1.expect)(homePage.consoleBanner.or(homePage.hubHeader)).toBeVisible({
|
|
428
|
+
timeout: 60000,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
// Camunda Hub replaces Console on SaaS (8.10 release notes), and the Hub
|
|
432
|
+
// shell has neither the "Camunda logo Console" link nor the "Open
|
|
433
|
+
// Organizations" switcher, and lands on /workspaces without the org id in
|
|
434
|
+
// the URL. Its sidebar scopes the Clusters/Organization links to the active
|
|
435
|
+
// org, so assert the expected org id there instead.
|
|
436
|
+
if (await homePage.hubHeader.isVisible()) {
|
|
437
|
+
await (0, test_1.expect)(homePage.hubSidebar
|
|
438
|
+
.locator(`a[href*="/org/${process.env.ORG_ID}/"]`)
|
|
439
|
+
.first()).toBeVisible({ timeout: 180000 });
|
|
440
|
+
return;
|
|
383
441
|
}
|
|
384
442
|
await (0, test_1.expect)(homePage.openOrganizationButton).toBeVisible({ timeout: 180000 });
|
|
385
443
|
try {
|
|
@@ -12,7 +12,8 @@ _8_7_1.test.describe('Cluster Setup Tests', () => {
|
|
|
12
12
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
13
13
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
14
14
|
});
|
|
15
|
-
|
|
15
|
+
// Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
|
|
16
|
+
_8_7_1.test.skip('Enable Alpha Features', async ({ page, loginPage, consoleOrganizationsPage, homePage, }, testInfo) => {
|
|
16
17
|
_8_7_1.test.skip(process.env.IS_PROD === 'true', 'Skipping test because not required on PROD test org');
|
|
17
18
|
_8_7_1.test.skip(process.env.IS_AG === 'true', 'Skipping test because not required when IS_AG is true');
|
|
18
19
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
@@ -60,10 +61,14 @@ _8_7_1.test.describe('Cluster Setup Tests', () => {
|
|
|
60
61
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, user, (testInfo.workerIndex + 1) * 1000);
|
|
61
62
|
});
|
|
62
63
|
await _8_7_1.test.step('Navigate to Web Modeler', async () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Camunda Hub replaced Console + Web Modeler on SaaS: it lands on the
|
|
65
|
+
// Modeler home directly and has no app switcher to open.
|
|
66
|
+
if (!new URL(page.url()).hostname.startsWith('hub.')) {
|
|
67
|
+
await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
|
|
68
|
+
timeout: 120000,
|
|
69
|
+
});
|
|
70
|
+
await appsPage.clickCamundaApps();
|
|
71
|
+
}
|
|
67
72
|
await appsPage.clickModeler();
|
|
68
73
|
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
69
74
|
timeout: 180000,
|
|
@@ -71,6 +76,14 @@ _8_7_1.test.describe('Cluster Setup Tests', () => {
|
|
|
71
76
|
});
|
|
72
77
|
await _8_7_1.test.step('Create Cross Component Project Folder', async () => {
|
|
73
78
|
await homePage.clickSkipCustomization();
|
|
79
|
+
// Do not attempt a "only User 1 creates, everyone else waits" fix for
|
|
80
|
+
// the parallel race here: the 8.10 sibling already tried exactly this
|
|
81
|
+
// and reverted it (see tests/8.10/test-setup.spec.ts) after finding
|
|
82
|
+
// the created workspace never became visible to the other users'
|
|
83
|
+
// sessions at all -- not a wait-budget problem, so no amount of
|
|
84
|
+
// retrying fixes it. Every user independently checking-and-creating
|
|
85
|
+
// still races occasionally, but that is the smaller, known failure
|
|
86
|
+
// mode; see camunda/c8-cross-component-e2e-tests#3649.
|
|
74
87
|
await modelerHomePage.createCrossComponentProjectFolder();
|
|
75
88
|
await modelerHomePage.clickHomeBreadcrumb();
|
|
76
89
|
await modelerHomePage.assertCrossComponentProjectFolderVisible();
|
|
@@ -134,13 +134,15 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
134
134
|
});
|
|
135
135
|
await (0, sleep_1.sleep)(30000);
|
|
136
136
|
await appsPage.clickCamundaApps();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
// clickCamundaApps() is a no-op on Hub (no global app-switcher panel to
|
|
138
|
+
// open -- see its own comment), so modelerLink has nothing to assert
|
|
139
|
+
// there: Hub's authenticated shell already proves Modeler access by
|
|
140
|
+
// having been reached at all. consoleLink's accessible name ("Console")
|
|
141
|
+
// matches Hub's persistent sidebar link unchanged, so that assertion
|
|
142
|
+
// still applies on both shells.
|
|
140
143
|
await (0, test_1.expect)(appsPage.consoleLink).toBeVisible();
|
|
141
|
-
await (
|
|
142
|
-
await (
|
|
143
|
-
await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
|
|
144
|
+
await appsPage.assertOperatePresent(clusterName, false);
|
|
145
|
+
await appsPage.assertTasklistPresent(clusterName, false);
|
|
144
146
|
await appsPage.clickModeler();
|
|
145
147
|
await homePage.clickSkipCustomization();
|
|
146
148
|
await modelerHomePage.createCrossComponentProjectFolder();
|
|
@@ -206,10 +208,9 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
206
208
|
await (0, test_1.expect)(appsPage.consoleLink).toBeVisible({
|
|
207
209
|
timeout: 30000,
|
|
208
210
|
});
|
|
209
|
-
await appsPage.assertOperatePresent();
|
|
210
|
-
await appsPage.assertTasklistPresent();
|
|
211
|
-
await (
|
|
212
|
-
await appsPage.assertOptimizePresent(false);
|
|
211
|
+
await appsPage.assertOperatePresent(clusterName);
|
|
212
|
+
await appsPage.assertTasklistPresent(clusterName);
|
|
213
|
+
await appsPage.assertOptimizePresent(clusterName, false);
|
|
213
214
|
await appsPage.clickModeler();
|
|
214
215
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
215
216
|
await modelerHomePage.clickProcessDiagram(processName);
|
|
@@ -266,10 +267,9 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
266
267
|
await (0, test_1.expect)(appsPage.consoleLink).toBeVisible({
|
|
267
268
|
timeout: 20000,
|
|
268
269
|
});
|
|
269
|
-
await appsPage.assertOptimizePresent();
|
|
270
|
-
await appsPage.assertOperatePresent();
|
|
271
|
-
await appsPage.assertTasklistPresent();
|
|
272
|
-
await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
|
|
270
|
+
await appsPage.assertOptimizePresent(clusterName);
|
|
271
|
+
await appsPage.assertOperatePresent(clusterName);
|
|
272
|
+
await appsPage.assertTasklistPresent(clusterName);
|
|
273
273
|
await appsPage.clickModeler();
|
|
274
274
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
275
275
|
await modelerHomePage.clickProcessDiagram(processName);
|
|
@@ -18,7 +18,8 @@ _8_9_1.test.describe('Cluster Setup Tests', () => {
|
|
|
18
18
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
19
19
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
20
20
|
});
|
|
21
|
-
|
|
21
|
+
// Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
|
|
22
|
+
_8_9_1.test.skip('Enable Alpha Features', async ({ page, loginPage, consoleOrganizationsPage, homePage, }, testInfo) => {
|
|
22
23
|
_8_9_1.test.skip(process.env.IS_PROD === 'true', 'Skipping test because not required on PROD test org');
|
|
23
24
|
_8_9_1.test.skip(process.env.IS_AG === 'true', 'Skipping test because not required when IS_AG is true');
|
|
24
25
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
@@ -128,11 +129,17 @@ _8_9_1.test.describe('Cluster Setup Tests', () => {
|
|
|
128
129
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, user, (testInfo.workerIndex + 1) * 1000);
|
|
129
130
|
});
|
|
130
131
|
await _8_9_1.test.step('Navigate to Web Modeler', async () => {
|
|
131
|
-
|
|
132
|
+
// Camunda Hub replaced Console + Web Modeler on SaaS: it lands on the
|
|
133
|
+
// Modeler home directly and has no app switcher to wait for.
|
|
134
|
+
await (0, test_1.expect)(homePage.camundaComponentsButton.or(homePage.hubHeader)).toBeVisible({
|
|
132
135
|
timeout: 120000,
|
|
133
136
|
});
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
// Camunda Hub (hub.*) replaced Console + Web Modeler on SaaS and has no
|
|
138
|
+
// app switcher: its home already is the Modeler home.
|
|
139
|
+
if (!new URL(page.url()).hostname.startsWith('hub.')) {
|
|
140
|
+
await appsPage.clickCamundaApps();
|
|
141
|
+
await appsPage.clickModeler();
|
|
142
|
+
}
|
|
136
143
|
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
137
144
|
timeout: 180000,
|
|
138
145
|
});
|
|
@@ -6,3 +6,8 @@ export declare const AUTH0_QUOTA_TEXT: RegExp;
|
|
|
6
6
|
export declare const AUTH0_GENERIC_ERROR_TEXT: RegExp;
|
|
7
7
|
export declare function classifyAuthErrorPage(page: Page): Promise<string | null>;
|
|
8
8
|
export declare function throwIfAuthErrorPage(page: Page): Promise<void>;
|
|
9
|
+
export declare const AUTH0_INVALID_CREDENTIALS = "AUTH0_INVALID_CREDENTIALS";
|
|
10
|
+
export declare const AUTH0_ACCOUNT_BLOCKED = "AUTH0_ACCOUNT_BLOCKED";
|
|
11
|
+
export declare const AUTH0_INVALID_CREDENTIALS_TEXT: RegExp;
|
|
12
|
+
export declare const AUTH0_ACCOUNT_BLOCKED_TEXT: RegExp;
|
|
13
|
+
export declare function classifyLoginRejection(page: Page, username: string): Promise<string | null>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.throwIfAuthErrorPage = exports.classifyAuthErrorPage = exports.AUTH0_GENERIC_ERROR_TEXT = exports.AUTH0_QUOTA_TEXT = exports.AUTH0_RATE_LIMIT_GENERIC = exports.AUTH0_RATE_LIMIT_QUOTA = exports.AUTH0_RATE_LIMIT = void 0;
|
|
3
|
+
exports.classifyLoginRejection = exports.AUTH0_ACCOUNT_BLOCKED_TEXT = exports.AUTH0_INVALID_CREDENTIALS_TEXT = exports.AUTH0_ACCOUNT_BLOCKED = exports.AUTH0_INVALID_CREDENTIALS = exports.throwIfAuthErrorPage = exports.classifyAuthErrorPage = exports.AUTH0_GENERIC_ERROR_TEXT = exports.AUTH0_QUOTA_TEXT = exports.AUTH0_RATE_LIMIT_GENERIC = exports.AUTH0_RATE_LIMIT_QUOTA = exports.AUTH0_RATE_LIMIT = void 0;
|
|
4
4
|
// Auth0 renders an error page for two structurally different failures, and
|
|
5
5
|
// until now both were counted as one signal. They need different responses:
|
|
6
6
|
//
|
|
@@ -93,3 +93,51 @@ async function throwIfAuthErrorPage(page) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
exports.throwIfAuthErrorPage = throwIfAuthErrorPage;
|
|
96
|
+
// Auth0 rejects a submitted password by re-rendering the same password step
|
|
97
|
+
// with one of these two messages, rather than serving a distinct error page
|
|
98
|
+
// -- so this needs its own probe after a password submission, unlike
|
|
99
|
+
// classifyAuthErrorPage above which covers the interstitial pages Auth0
|
|
100
|
+
// serves before any credentials are entered.
|
|
101
|
+
//
|
|
102
|
+
// Both are credential/provisioning-level rejections: the password Auth0 has
|
|
103
|
+
// on file for this account does not match what the runner was given, or the
|
|
104
|
+
// account is locked. Neither self-heals, so loginWithRetry treats this
|
|
105
|
+
// marker as fail-fast rather than the default retry-with-backoff -- retrying
|
|
106
|
+
// with the same env-supplied credentials 5 times only reproduces the same
|
|
107
|
+
// 400 response 5 times (see docs/saas-login-investigation.md).
|
|
108
|
+
exports.AUTH0_INVALID_CREDENTIALS = 'AUTH0_INVALID_CREDENTIALS';
|
|
109
|
+
exports.AUTH0_ACCOUNT_BLOCKED = 'AUTH0_ACCOUNT_BLOCKED';
|
|
110
|
+
exports.AUTH0_INVALID_CREDENTIALS_TEXT = /Wrong email or password/i;
|
|
111
|
+
exports.AUTH0_ACCOUNT_BLOCKED_TEXT = /account has been blocked after multiple consecutive login attempts/i;
|
|
112
|
+
// Auth0 re-renders the password step with the rejection copy right after the
|
|
113
|
+
// POST, but not necessarily by the moment the caller checks -- a zero-wait
|
|
114
|
+
// isVisible() can run before that paint completes and miss it, which would
|
|
115
|
+
// silently fall through to the generic ~120s shell wait instead of failing
|
|
116
|
+
// fast. Give each marker a short window to render.
|
|
117
|
+
const REJECTION_RENDER_TIMEOUT = 3000;
|
|
118
|
+
async function classifyLoginRejection(page, username) {
|
|
119
|
+
const [invalid, blocked] = await Promise.all([
|
|
120
|
+
page
|
|
121
|
+
.getByText(exports.AUTH0_INVALID_CREDENTIALS_TEXT)
|
|
122
|
+
.first()
|
|
123
|
+
.waitFor({ state: 'visible', timeout: REJECTION_RENDER_TIMEOUT })
|
|
124
|
+
.then(() => true)
|
|
125
|
+
.catch(() => false),
|
|
126
|
+
page
|
|
127
|
+
.getByText(exports.AUTH0_ACCOUNT_BLOCKED_TEXT)
|
|
128
|
+
.first()
|
|
129
|
+
.waitFor({ state: 'visible', timeout: REJECTION_RENDER_TIMEOUT })
|
|
130
|
+
.then(() => true)
|
|
131
|
+
.catch(() => false),
|
|
132
|
+
]);
|
|
133
|
+
if (!invalid && !blocked) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const marker = blocked ? exports.AUTH0_ACCOUNT_BLOCKED : exports.AUTH0_INVALID_CREDENTIALS;
|
|
137
|
+
const reason = blocked
|
|
138
|
+
? 'account has been blocked after multiple consecutive login attempts'
|
|
139
|
+
: 'wrong email or password';
|
|
140
|
+
return (`${marker}: Auth0 rejected the credentials for ${username} (${reason}) ` +
|
|
141
|
+
'-- this is a credential/provisioning issue, not something a retry fixes');
|
|
142
|
+
}
|
|
143
|
+
exports.classifyLoginRejection = classifyLoginRejection;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Page } from '@playwright/test';
|
|
2
|
+
export declare function resetHubBearerToken(page: Page): void;
|
|
2
3
|
export declare function deleteClusterViaApi(page: Page, clusterName: string): Promise<void>;
|
|
3
4
|
export declare function createClusterViaApi(page: Page, clusterName: string, region?: string, tasklistV2Enabled?: boolean): Promise<string>;
|
|
4
5
|
export declare function ensureClusterViaApi(page: Page, clusterName: string, region?: string, tasklistV2Enabled?: boolean): Promise<string>;
|