@camunda/e2e-test-suite 0.0.1189 → 0.0.1191

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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LoginPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const UtilitiesPage_1 = require("./UtilitiesPage");
6
+ const authErrorClassification_1 = require("../../utils/authErrorClassification");
6
7
  const userNameInputValidInputSchema = {
7
8
  _schema: {
8
9
  maxlength: 254,
@@ -47,9 +48,14 @@ class LoginPage {
47
48
  // missing this detection entirely, so a rate-limited login here fell
48
49
  // through to the generic 10-20s retry backoff instead of the 60-90s
49
50
  // needed to outlast a typical rate-limit window.
51
+ //
52
+ // Kept as one locator because every caller wants to stop waiting on
53
+ // either page; which of the two rendered is decided at throw time by
54
+ // classifyAuthErrorPage, since only that distinction says whether the
55
+ // remedy is a higher limit or an allow-listed runner IP.
50
56
  this.rateLimitError = page
51
- .getByText(/rate limit has been reached/i)
52
- .or(page.getByText(/We are sorry, an error occurred/i));
57
+ .getByText(authErrorClassification_1.AUTH0_QUOTA_TEXT)
58
+ .or(page.getByText(authErrorClassification_1.AUTH0_GENERIC_ERROR_TEXT));
53
59
  this.loginButton = page.getByRole('button', {
54
60
  name: 'Continue',
55
61
  exact: true,
@@ -106,13 +112,10 @@ class LoginPage {
106
112
  }
107
113
  // Surface a rate-limit page with the marker loginWithRetry keys its
108
114
  // 60-90s backoff on, rather than burning the remaining attempts on a
109
- // form the provider is not going to serve.
110
- if (await this.rateLimitError
111
- .first()
112
- .isVisible()
113
- .catch(() => false)) {
114
- throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
115
- }
115
+ // form the provider is not going to serve. The marker also records
116
+ // which of the two Auth0 failure modes this was, and the error copy
117
+ // behind it, so a recurrence can be diagnosed from the run log alone.
118
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(this.page);
116
119
  // Neither the password step nor the email step is on screen: some
117
120
  // interstitial we do not model. Hand back to fillPassword's own wait
118
121
  // instead of re-typing blindly into a page we cannot identify.
@@ -16,8 +16,12 @@ class OCIdentityHomePage {
16
16
  noPermissionsMessage;
17
17
  constructor(page) {
18
18
  this.page = page;
19
+ // The Admin redesign (new design system, enabled by default on SaaS) moved
20
+ // the header off the old Carbon shell: its logo link now carries the static
21
+ // aria-label "Camunda Admin" (AppHeaderV2 in camunda/camunda's
22
+ // identity/client), replacing the old "Camunda logo Admin" accessible name.
19
23
  this.adminBanner = page.getByRole('link', {
20
- name: 'Camunda logo Admin',
24
+ name: 'Camunda Admin',
21
25
  });
22
26
  this.groupsTab = page
23
27
  .getByRole('banner')
@@ -2,17 +2,16 @@ import { Page, Locator } from '@playwright/test';
2
2
  declare class TaskPanelPage {
3
3
  private page;
4
4
  readonly availableTasks: Locator;
5
- readonly filterPanel: Locator;
5
+ readonly filterSelectButton: Locator;
6
6
  readonly taskListPageBanner: Locator;
7
7
  readonly processesPageTab: Locator;
8
8
  readonly tasksTab: Locator;
9
9
  readonly noPermissionsMessage: Locator;
10
- readonly expandButton: Locator;
11
10
  readonly processesInfoText: Locator;
12
11
  constructor(page: Page);
13
12
  taskInList(name: string): Locator;
14
13
  openTask(name: string, totalTimeout?: number): Promise<void>;
15
- filterBy(option: 'All open' | 'Unassigned' | 'Assigned to me' | 'Completed'): Promise<void>;
14
+ filterBy(option: 'All open tasks' | 'Unassigned' | 'Assigned to me' | 'Completed'): Promise<void>;
16
15
  taskCount(name: string): Promise<number>;
17
16
  clickTasksTab(): Promise<void>;
18
17
  scrollToLastTask(name: string): Promise<void>;
@@ -8,17 +8,25 @@ const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLoc
8
8
  class TaskPanelPage {
9
9
  page;
10
10
  availableTasks;
11
- filterPanel;
11
+ filterSelectButton;
12
12
  taskListPageBanner;
13
13
  processesPageTab;
14
14
  tasksTab;
15
15
  noPermissionsMessage;
16
- expandButton;
17
16
  processesInfoText;
18
17
  constructor(page) {
19
18
  this.page = page;
20
19
  this.availableTasks = page.getByTitle('Available tasks');
21
- this.filterPanel = page.locator('[aria-controls="task-nav-bar"]');
20
+ // The old Carbon expandable filter sidebar (aria-controls="task-nav-bar",
21
+ // "Expand to show filters") is gone after Tasklist's redesign. Filtering is
22
+ // now a single dropdown-trigger button (id="filter-select") whose
23
+ // accessible name is the static "Filters" aria-label; clicking it opens a
24
+ // Radix DropdownMenu of filter options (matches the current locator in
25
+ // camunda/camunda's own qa/c8-orchestration-cluster-e2e-test-suite).
26
+ this.filterSelectButton = page.getByRole('button', {
27
+ name: 'Filters',
28
+ exact: true,
29
+ });
22
30
  // The header logo's accessible name is now just "Camunda logo" -- the
23
31
  // per-app suffix ("Camunda logo Tasklist") was dropped when Tasklist's
24
32
  // header moved off the old Carbon shell (matches the already-corrected
@@ -29,9 +37,6 @@ class TaskPanelPage {
29
37
  this.processesPageTab = page.getByRole('link', { name: 'Processes' });
30
38
  this.tasksTab = page.getByRole('link', { name: 'Tasks' });
31
39
  this.noPermissionsMessage = page.getByText('You don’t have access to this component');
32
- this.expandButton = page.getByRole('button', {
33
- name: 'Expand to show filters',
34
- });
35
40
  this.processesInfoText = page.getByText('Browse and run processes published by your organization');
36
41
  }
37
42
  // Tasklist's importer can lag well behind Zeebe under heavy concurrent load
@@ -82,21 +87,46 @@ class TaskPanelPage {
82
87
  `${totalTimeout}ms: ${lastError}`);
83
88
  }
84
89
  async filterBy(option) {
85
- try {
86
- await this.filterPanel.click({ timeout: 60000 });
87
- await (0, test_1.expect)(this.page.getByRole('link', { name: option }).getByText(option)).toBeVisible({ timeout: 60000 });
88
- await this.page
89
- .getByRole('link', { name: option })
90
- .getByText(option)
91
- .click({ timeout: 60000 });
92
- }
93
- catch {
94
- await this.expandButton.click({ timeout: 60000 });
95
- await this.page
96
- .getByRole('link', { name: option })
97
- .getByText(option)
98
- .click({ timeout: 60000 });
90
+ // Open the always-visible filter dropdown, then pick the option from the
91
+ // Radix menu that appears (role="menuitem"). There is no separate
92
+ // expand/collapse step like the old Carbon sidebar had.
93
+ //
94
+ // The trigger also carries the active filter's label, so it doubles as
95
+ // the "selection applied" signal, replacing the heading the old Carbon
96
+ // panel rendered for the active filter.
97
+ //
98
+ // TasksLayoutPage renders <Filters disabled={isPending} />, so a task
99
+ // list refetch landing while the menu is open disables the trigger and
100
+ // tears the menu down before Radix runs the item's onSelect: the menu
101
+ // closes but the filter never changes. Re-open and pick again rather
102
+ // than returning with the list still on the previous filter (same
103
+ // bounded retry as pages/c8Run-8.10/TaskPanelPage.ts).
104
+ const menuItem = this.page.getByRole('menuitem', {
105
+ name: option,
106
+ exact: true,
107
+ });
108
+ const triggerShowingOption = this.filterSelectButton.filter({
109
+ hasText: new RegExp(`^${option}$`),
110
+ });
111
+ const maxAttempts = 3;
112
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
113
+ await (0, test_1.expect)(this.filterSelectButton).toBeEnabled({ timeout: 60000 });
114
+ await this.filterSelectButton.click();
115
+ await (0, test_1.expect)(menuItem).toBeVisible({ timeout: 10000 });
116
+ await menuItem.click();
117
+ const applied = await triggerShowingOption
118
+ .waitFor({ state: 'visible', timeout: 15000 })
119
+ .then(() => true)
120
+ .catch(() => false);
121
+ if (applied) {
122
+ return;
123
+ }
124
+ console.warn(`Attempt ${attempt} to apply the "${option}" task filter did not ` +
125
+ 'take effect. Retrying...');
126
+ await this.page.keyboard.press('Escape');
127
+ await (0, test_1.expect)(this.filterSelectButton).toHaveAttribute('aria-expanded', 'false', { timeout: 10000 });
99
128
  }
129
+ throw new Error(`Task filter "${option}" was not applied after ${maxAttempts} attempts`);
100
130
  }
101
131
  async taskCount(name) {
102
132
  await (0, sleep_1.sleep)(20000);
@@ -6,6 +6,7 @@ const ModelerHomePage_1 = require("./ModelerHomePage");
6
6
  const HomePage_1 = require("./HomePage");
7
7
  const sleep_1 = require("../../utils/sleep");
8
8
  const randomSleep_1 = require("../../utils/randomSleep");
9
+ const authErrorClassification_1 = require("../../utils/authErrorClassification");
9
10
  const LoginPage_1 = require("./LoginPage");
10
11
  const fileUpload_1 = require("../../utils/fileUpload");
11
12
  const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
@@ -53,12 +54,7 @@ async function waitForLoginForm(page, loginPage, totalTimeout = 90000, roundTime
53
54
  // whole budget waiting for a login form that will never appear. Bailing
54
55
  // out immediately with a typed marker lets loginWithRetry's caller back
55
56
  // off long enough to actually outlast the rate-limit window instead.
56
- if (await loginPage.rateLimitError
57
- .first()
58
- .isVisible()
59
- .catch(() => false)) {
60
- throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
61
- }
57
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(page);
62
58
  try {
63
59
  // Confirm we reached the login page before filling credentials.
64
60
  // Without this check the locator for 'Email address' can match a hidden
@@ -172,7 +168,7 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
172
168
  catch (error) {
173
169
  lastError = error;
174
170
  if (attempt < maxRetries - 1) {
175
- const rateLimited = String(error).includes('AUTH0_RATE_LIMIT');
171
+ const rateLimited = String(error).includes(authErrorClassification_1.AUTH0_RATE_LIMIT);
176
172
  console.warn(`Attempt ${attempt + 1} failed for logging in. Retrying with clean session...`, error);
177
173
  if (rateLimited) {
178
174
  // Auth0 rate-limit windows commonly last 30-90s. Back off long
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LoginPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const authErrorClassification_1 = require("../../utils/authErrorClassification");
5
6
  const UtilitiesPage_1 = require("./UtilitiesPage");
6
7
  const sessionRecovery_1 = require("../../utils/sessionRecovery");
7
8
  class LoginPage {
@@ -37,8 +38,8 @@ class LoginPage {
37
38
  });
38
39
  this.errorMessage = page.getByText('Wrong email or password');
39
40
  this.rateLimitError = page
40
- .getByText(/rate limit has been reached/i)
41
- .or(page.getByText(/We are sorry, an error occurred/i));
41
+ .getByText(authErrorClassification_1.AUTH0_QUOTA_TEXT)
42
+ .or(page.getByText(authErrorClassification_1.AUTH0_GENERIC_ERROR_TEXT));
42
43
  }
43
44
  async fillUsername(username) {
44
45
  await this.usernameInput.waitFor({ state: 'visible', timeout: 60000 });
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.waitForLoadingToFinish = exports.assertTestUsesCorrectOrganizationFromModeler = exports.expectCountToBeOneOf = exports.createAPIClient = exports.modelAndRunConnectorsTimerEventDiagram = exports.modelAndRunConnectorsDocHandlingDiagram = exports.deleteCluster = exports.deleteAllUserGroups = exports.assertTestUsesCorrectOrganization = exports.clickInvitationLinkInEmail = exports.assertLatestAlertEmail = exports.enableAuthorizations = exports.disableRBA = exports.runMultipleProcesses = exports.modelDiagramFromFile = exports.runProcess = exports.completeTaskWithRetry = exports.assertPageTextWithRetry = exports.assertLocatorVisibleWithRetry = exports.modelRestConnector = exports.loginWithRetry = exports.forceLogoutIfNeeded = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const authErrorClassification_1 = require("../../utils/authErrorClassification");
5
6
  const ModelerHomePage_1 = require("./ModelerHomePage");
6
7
  const HomePage_1 = require("./HomePage");
7
8
  const sleep_1 = require("../../utils/sleep");
@@ -80,12 +81,7 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
80
81
  // the usual auth controls. Bail out early on it so the outer retry backs
81
82
  // off long enough to outlast the window instead of burning attempts
82
83
  // against a 60s wait for a login form that will never render.
83
- if (await loginPage.rateLimitError
84
- .first()
85
- .isVisible()
86
- .catch(() => false)) {
87
- throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
88
- }
84
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(page);
89
85
  await (0, test_1.expect)(loginPage.loginMessage.or(loginPage.passwordHeading)).toBeVisible({ timeout: 60000 });
90
86
  await loginPage.loginWithTestUser(testUser);
91
87
  return;
@@ -93,7 +89,7 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
93
89
  catch (error) {
94
90
  lastError = error;
95
91
  if (attempt < maxRetries - 1) {
96
- const rateLimited = String(error).includes('AUTH0_RATE_LIMIT');
92
+ const rateLimited = String(error).includes(authErrorClassification_1.AUTH0_RATE_LIMIT);
97
93
  console.warn(`Attempt ${attempt + 1} failed for logging in. Retrying with clean session...`, error);
98
94
  if (rateLimited) {
99
95
  // Auth0 rate-limit windows commonly last 30-90s. Back off long
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LoginPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const authErrorClassification_1 = require("../../utils/authErrorClassification");
5
6
  const UtilitiesPage_1 = require("./UtilitiesPage");
6
7
  const userNameInputValidInputSchema = {
7
8
  _schema: {
@@ -46,8 +47,8 @@ class LoginPage {
46
47
  });
47
48
  this.errorMessage = page.getByText('Wrong email or password');
48
49
  this.rateLimitError = page
49
- .getByText(/rate limit has been reached/i)
50
- .or(page.getByText(/We are sorry, an error occurred/i));
50
+ .getByText(authErrorClassification_1.AUTH0_QUOTA_TEXT)
51
+ .or(page.getByText(authErrorClassification_1.AUTH0_GENERIC_ERROR_TEXT));
51
52
  }
52
53
  async fillUsername(username) {
53
54
  await (0, test_1.expect)(this.usernameInput.locator).toBeVisible({ timeout: 120000 });
@@ -127,7 +128,11 @@ class LoginPage {
127
128
  .catch(() => false),
128
129
  ]);
129
130
  if (rateLimitVisible) {
130
- throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
131
+ // Classify which of the two Auth0 failure modes this was, and capture
132
+ // the error copy, so a recurrence is diagnosable from the run log.
133
+ // Falls back to the bare marker if the page moved on in between.
134
+ throw new Error((await (0, authErrorClassification_1.classifyAuthErrorPage)(this.page)) ??
135
+ `${authErrorClassification_1.AUTH0_RATE_LIMIT}: identity provider returned an error page`);
131
136
  }
132
137
  if (passwordHeadingVisible || passwordInputVisible) {
133
138
  authStep = 'password';
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.waitForLoadingToFinish = exports.assertTestUsesCorrectOrganizationFromModeler = exports.expectCountToBeOneOf = exports.createAPIClient = exports.modelAndRunConnectorsTimerEventDiagram = exports.modelAndRunConnectorsDocHandlingDiagram = exports.deleteCluster = exports.deleteAllUserGroups = exports.assertTestUsesCorrectOrganization = exports.assertLatestAlertEmail = exports.clickInvitationLinkInEmail = exports.enableAuthorizations = exports.disableRBA = exports.runMultipleProcesses = exports.runProcess = exports.completeTaskWithRetry = exports.assertPageTextWithRetry = exports.assertLocatorVisibleWithRetry = exports.modelRestConnector = exports.modelDiagramFromFile = exports.loginWithRetry = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const authErrorClassification_1 = require("../../utils/authErrorClassification");
5
6
  const ModelerHomePage_1 = require("./ModelerHomePage");
6
7
  const HomePage_1 = require("./HomePage");
7
8
  const sleep_1 = require("../../utils/sleep");
@@ -51,12 +52,7 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
51
52
  // The identity provider occasionally returns a generic error / rate
52
53
  // limit page that has none of the usual auth controls. Bail out early
53
54
  // when that page is visible so the outer retry can back off.
54
- if (await loginPage.rateLimitError
55
- .first()
56
- .isVisible()
57
- .catch(() => false)) {
58
- throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
59
- }
55
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(page);
60
56
  // Auth0 may restore directly on password entry depending on session
61
57
  // history, so allow either identifier screen or password screen.
62
58
  await (0, test_1.expect)(loginPage.loginMessage.or(loginPage.passwordHeading)).toBeVisible({ timeout: 60000 });
@@ -66,7 +62,7 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
66
62
  catch (error) {
67
63
  lastError = error;
68
64
  if (attempt < maxRetries - 1) {
69
- const rateLimited = String(error).includes('AUTH0_RATE_LIMIT');
65
+ const rateLimited = String(error).includes(authErrorClassification_1.AUTH0_RATE_LIMIT);
70
66
  console.warn(`Attempt ${attempt + 1} failed for logging in. Retrying with clean session...`, error);
71
67
  if (rateLimited) {
72
68
  // Auth0 rate-limit windows commonly last 30-90s. Back off long
@@ -0,0 +1,8 @@
1
+ import { Page } from '@playwright/test';
2
+ export declare const AUTH0_RATE_LIMIT = "AUTH0_RATE_LIMIT";
3
+ export declare const AUTH0_RATE_LIMIT_QUOTA = "AUTH0_RATE_LIMIT_QUOTA";
4
+ export declare const AUTH0_RATE_LIMIT_GENERIC = "AUTH0_RATE_LIMIT_GENERIC";
5
+ export declare const AUTH0_QUOTA_TEXT: RegExp;
6
+ export declare const AUTH0_GENERIC_ERROR_TEXT: RegExp;
7
+ export declare function classifyAuthErrorPage(page: Page): Promise<string | null>;
8
+ export declare function throwIfAuthErrorPage(page: Page): Promise<void>;
@@ -0,0 +1,95 @@
1
+ "use strict";
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;
4
+ // Auth0 renders an error page for two structurally different failures, and
5
+ // until now both were counted as one signal. They need different responses:
6
+ //
7
+ // QUOTA - the tenant's request rate limit ("...rate limit has been
8
+ // reached"). This is what moving the tenant to a Production
9
+ // environment raises.
10
+ // GENERIC - Auth0's catch-all error page. Attack Protection blocks
11
+ // (suspicious IP throttling, brute-force protection) surface here,
12
+ // and those are governed by their own settings rather than the
13
+ // tenant's rate limits, so a Production environment does nothing
14
+ // for them. A CI runner signing in as ~19 freshly created accounts
15
+ // from one egress IP inside a few minutes is precisely the pattern
16
+ // they exist to detect.
17
+ //
18
+ // Telling them apart matters because the remedies do not overlap: one is a
19
+ // limit to raise, the other is a source IP to allow-list. Counting them as one
20
+ // number is why we could not say which we were hitting.
21
+ //
22
+ // Both markers keep the AUTH0_RATE_LIMIT prefix, so the existing
23
+ // String(error).includes(AUTH0_RATE_LIMIT) backoff checks match unchanged.
24
+ exports.AUTH0_RATE_LIMIT = 'AUTH0_RATE_LIMIT';
25
+ exports.AUTH0_RATE_LIMIT_QUOTA = `${exports.AUTH0_RATE_LIMIT}_QUOTA`;
26
+ exports.AUTH0_RATE_LIMIT_GENERIC = `${exports.AUTH0_RATE_LIMIT}_GENERIC`;
27
+ exports.AUTH0_QUOTA_TEXT = /rate limit has been reached/i;
28
+ exports.AUTH0_GENERIC_ERROR_TEXT = /We are sorry, an error occurred/i;
29
+ const MAX_DETAIL_CHARS = 300;
30
+ // The error page's own copy and error code are what separate the two
31
+ // mechanisms, so capture them rather than only the fact that some error
32
+ // rendered. Deliberately narrow: host plus path, and Auth0's two error query
33
+ // params, never the whole URL - on an OAuth redirect that can carry `code` and
34
+ // `state`, which do not belong in a CI log.
35
+ async function readErrorContext(page) {
36
+ const parts = [];
37
+ try {
38
+ const url = new URL(page.url());
39
+ parts.push(`${url.host}${url.pathname}`);
40
+ for (const key of ['error', 'error_description']) {
41
+ const value = url.searchParams.get(key);
42
+ if (value) {
43
+ parts.push(`${key}=${value}`);
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ // page.url() is not always parsable (about:blank on a torn-down context).
49
+ }
50
+ try {
51
+ const text = await page.locator('body').innerText({ timeout: 5000 });
52
+ const collapsed = text.replace(/\s+/g, ' ').trim();
53
+ if (collapsed) {
54
+ parts.push(`text="${collapsed.slice(0, MAX_DETAIL_CHARS)}"`);
55
+ }
56
+ }
57
+ catch {
58
+ // Best-effort only: this runs on a failure path and must never replace
59
+ // the caller's diagnosis with its own.
60
+ }
61
+ return parts.join(' | ');
62
+ }
63
+ // Returns the message to throw when an Auth0 error page is on screen, or null
64
+ // when it is not. Never throws, for the reason above.
65
+ async function classifyAuthErrorPage(page) {
66
+ const [quota, generic] = await Promise.all([
67
+ page
68
+ .getByText(exports.AUTH0_QUOTA_TEXT)
69
+ .first()
70
+ .isVisible()
71
+ .catch(() => false),
72
+ page
73
+ .getByText(exports.AUTH0_GENERIC_ERROR_TEXT)
74
+ .first()
75
+ .isVisible()
76
+ .catch(() => false),
77
+ ]);
78
+ if (!quota && !generic) {
79
+ return null;
80
+ }
81
+ // Quota wins when both match: its copy is the specific diagnosis, and the
82
+ // generic banner can wrap it.
83
+ const marker = quota ? exports.AUTH0_RATE_LIMIT_QUOTA : exports.AUTH0_RATE_LIMIT_GENERIC;
84
+ const context = await readErrorContext(page);
85
+ const summary = `${marker}: identity provider returned an error page`;
86
+ return context ? `${summary} | ${context}` : summary;
87
+ }
88
+ exports.classifyAuthErrorPage = classifyAuthErrorPage;
89
+ async function throwIfAuthErrorPage(page) {
90
+ const message = await classifyAuthErrorPage(page);
91
+ if (message) {
92
+ throw new Error(message);
93
+ }
94
+ }
95
+ exports.throwIfAuthErrorPage = throwIfAuthErrorPage;
@@ -1,3 +1,4 @@
1
1
  import { Page } from '@playwright/test';
2
- export declare const AUTH0_RATE_LIMIT = "AUTH0_RATE_LIMIT";
2
+ import { AUTH0_RATE_LIMIT } from './authErrorClassification';
3
+ export { AUTH0_RATE_LIMIT };
3
4
  export declare function loginToApp(appName: string, page: Page): Promise<void>;
@@ -3,16 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loginToApp = exports.AUTH0_RATE_LIMIT = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const LoginPage_1 = require("../pages/8.8/LoginPage");
6
- exports.AUTH0_RATE_LIMIT = 'AUTH0_RATE_LIMIT';
7
- async function throwIfRateLimited(loginPage) {
8
- const rateLimited = await loginPage.rateLimitError
9
- .first()
10
- .isVisible()
11
- .catch(() => false);
12
- if (rateLimited) {
13
- throw new Error(`${exports.AUTH0_RATE_LIMIT}: identity provider returned an error page`);
14
- }
15
- }
6
+ const authErrorClassification_1 = require("./authErrorClassification");
7
+ Object.defineProperty(exports, "AUTH0_RATE_LIMIT", { enumerable: true, get: function () { return authErrorClassification_1.AUTH0_RATE_LIMIT; } });
16
8
  async function loginToApp(appName, page) {
17
9
  await page.goto(appName, { waitUntil: 'domcontentloaded', timeout: 60000 });
18
10
  // 8.8 SaaS occasionally experiences 429s on app.js during navigation rendering,
@@ -25,13 +17,13 @@ async function loginToApp(appName, page) {
25
17
  // caller can tell a rate limit apart from a slow login form, and the caller
26
18
  // needs that distinction to pick a backoff long enough to outlast the window.
27
19
  await (0, test_1.expect)(loginPage.usernameInput.or(loginPage.rateLimitError.first())).toBeVisible({ timeout: 60000 });
28
- await throwIfRateLimited(loginPage);
20
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(page);
29
21
  await loginPage.fillUsername(process.env.C8_USERNAME);
30
22
  await (0, test_1.expect)(loginPage.usernameInput).toHaveValue(process.env.C8_USERNAME);
31
23
  await (0, test_1.expect)(loginPage.loginMessage).toBeVisible();
32
24
  await loginPage.clickContinueButton();
33
25
  await (0, test_1.expect)(loginPage.passwordInput.or(loginPage.rateLimitError.first())).toBeVisible({ timeout: 90000 });
34
- await throwIfRateLimited(loginPage);
26
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(page);
35
27
  await loginPage.fillPassword(process.env.C8_PASSWORD);
36
28
  await (0, test_1.expect)(loginPage.passwordHeading).toBeVisible({ timeout: 90000 });
37
29
  await loginPage.clickLoginButton();
@@ -44,7 +36,7 @@ async function loginToApp(appName, page) {
44
36
  catch (error) {
45
37
  // Auth0 can also serve the rate-limit page as the response to the password
46
38
  // submit, in which case the redirect back to appName never happens.
47
- await throwIfRateLimited(loginPage);
39
+ await (0, authErrorClassification_1.throwIfAuthErrorPage)(page);
48
40
  throw error;
49
41
  }
50
42
  await page
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1189",
3
+ "version": "0.0.1191",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",