@camunda/e2e-test-suite 0.0.1195 → 0.0.1197

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.
@@ -2,9 +2,11 @@ import { Page, Locator } from '@playwright/test';
2
2
  declare class TaskPanelPage {
3
3
  private page;
4
4
  readonly availableTasks: Locator;
5
- readonly filterOptions: Locator;
5
+ readonly filterPanelExpandButton: Locator;
6
+ readonly filterPanelCollapseButton: Locator;
7
+ readonly filterSelectButton: Locator;
8
+ readonly filterControls: Locator;
6
9
  readonly tasklistBanner: Locator;
7
- readonly collapseFilter: Locator;
8
10
  readonly processesPageTab: Locator;
9
11
  readonly tasksTab: Locator;
10
12
  readonly assignToMeButton: Locator;
@@ -14,8 +16,9 @@ declare class TaskPanelPage {
14
16
  constructor(page: Page);
15
17
  openTask(name: string, assignButton?: boolean): Promise<void>;
16
18
  filterBy(option: 'All open' | 'Unassigned' | 'Assigned to me' | 'Completed'): Promise<void>;
17
- private expandFilterPanel;
19
+ private openFilterControls;
18
20
  private static appliedFilterUrl;
21
+ activeFilter(option: string): Locator;
19
22
  clickCollapseFilter(): Promise<void>;
20
23
  scrollToLastTask(name: string): Promise<void>;
21
24
  scrollToFirstTask(name: string): Promise<void>;
@@ -6,9 +6,11 @@ const constants_1 = require("../../utils/constants");
6
6
  class TaskPanelPage {
7
7
  page;
8
8
  availableTasks;
9
- filterOptions;
9
+ filterPanelExpandButton;
10
+ filterPanelCollapseButton;
11
+ filterSelectButton;
12
+ filterControls;
10
13
  tasklistBanner;
11
- collapseFilter;
12
14
  processesPageTab;
13
15
  tasksTab;
14
16
  assignToMeButton;
@@ -18,9 +20,36 @@ class TaskPanelPage {
18
20
  constructor(page) {
19
21
  this.page = page;
20
22
  this.availableTasks = page.getByTitle('Available tasks');
21
- this.filterOptions = page
22
- .locator('[aria-label="Filter controls"] li')
23
- .filter({ hasText: 'Expand to show filters' });
23
+ // SM-8.10 is tested against two different Tasklist shells, so every filter
24
+ // locator below has to match either one.
25
+ //
26
+ // `stable/8.10` -- which is what the published `8.10-SNAPSHOT` images this
27
+ // repo's own CI deploys are built from -- still renders the Carbon
28
+ // `CollapsiblePanel`: a collapsed rail carrying the expand control, an
29
+ // expanded `<nav id="task-nav-bar">` listing each filter as a `<Link>`,
30
+ // and an `<h1>` naming the active filter.
31
+ // camunda/camunda `main` is 8.11.0-SNAPSHOT and has replaced that with the
32
+ // Design System `FilterSelect`: one always-visible trigger (accessible name
33
+ // "Filters") opening a menu of `menuitem`s, no expand/collapse control and
34
+ // no `<h1>`. The monorepo's helm e2e deploys an image built from that
35
+ // commit, so it is the shell seen there.
36
+ // Matching only one shell is what made this pair of surfaces trade
37
+ // failures: targeting the Carbon rail found no expand control on the
38
+ // monorepo run, and targeting the dropdown found no "Filters" button at
39
+ // all here (`<element(s) not found>`).
40
+ this.filterPanelExpandButton = page.locator('button[aria-controls="task-nav-bar"][aria-expanded="false"]');
41
+ this.filterPanelCollapseButton = page.locator('button[aria-controls="task-nav-bar"][aria-expanded="true"]');
42
+ this.filterSelectButton = page.getByRole('button', {
43
+ name: 'Filters',
44
+ exact: true,
45
+ });
46
+ // "the task panel's filter control is on screen", whichever shell renders
47
+ // it: the Carbon rail shows the expand control when collapsed and the
48
+ // collapse control when expanded, never both.
49
+ this.filterControls = this.filterPanelExpandButton
50
+ .or(this.filterPanelCollapseButton)
51
+ .or(this.filterSelectButton)
52
+ .first();
24
53
  // Same Design System header rollout NavigationPage.tasklistPageBanner
25
54
  // covers: "Tasklist" is a breadcrumb link once the Carbon shell is gone,
26
55
  // and "Camunda logo Tasklist" while it is still served.
@@ -29,7 +58,6 @@ class TaskPanelPage {
29
58
  .getByRole('link', { name: 'Tasklist', exact: true })
30
59
  .or(page.getByRole('link', { name: 'Camunda logo Tasklist' }))
31
60
  .first();
32
- this.collapseFilter = page.locator('button[aria-controls="task-nav-bar"][aria-expanded="true"]');
33
61
  this.processesPageTab = page.getByRole('link', { name: 'Processes' });
34
62
  this.tasksTab = page.getByRole('link', { name: 'Tasks', exact: true });
35
63
  this.assignToMeButton = page.getByRole('button', { name: 'Assign to me' });
@@ -72,28 +100,37 @@ class TaskPanelPage {
72
100
  }
73
101
  }
74
102
  async filterBy(option) {
103
+ // Both shells list the options only once their control is open, so open it
104
+ // first and then pick from whichever of the two the deployed build renders:
105
+ // a `<Link>` in the Carbon panel's expanded nav, or a `role="menuitem"` in
106
+ // the Design System dropdown. The Carbon links are scoped to #task-nav-bar
107
+ // so "Completed" cannot pick up a custom filter or a task row carrying the
108
+ // same words. Both shells spell the default filter "All open tasks" while
109
+ // callers pass "All open".
110
+ //
75
111
  // The task list refetches every 5s behind a suspense boundary, so a poll
76
- // landing on the filter click tears the floating filter panel down before
77
- // the router navigates: the panel closes but the filter never changes.
78
- // Nothing downstream notices, so the test fails much later on an assertion
79
- // that reads the still-unfiltered list. Confirm the route actually
80
- // carries the filter, and re-pick if it does not.
112
+ // landing on the pick can tear the control down before the router
113
+ // navigates: it closes but the filter never changes, and the test fails
114
+ // much later on an assertion that reads the still-unfiltered list. Confirm
115
+ // the route actually carries the filter, and re-pick if it does not.
116
+ const optionName = option === 'All open' ? 'All open tasks' : option;
117
+ const filterOption = this.page
118
+ .locator('#task-nav-bar')
119
+ .getByRole('link', { name: optionName, exact: true })
120
+ .or(this.page.getByRole('menuitem', { name: optionName, exact: true }));
81
121
  const maxAttempts = 3;
82
122
  let lastError;
83
123
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
84
124
  try {
85
- await this.expandFilterPanel();
86
- // Deliberately not `exact`: the panel spells the default filter
87
- // "All open tasks" while callers pass "All open".
88
- const filterLink = this.page.getByRole('link', { name: option });
89
- // Only the first attempt waits out a slow first render. Later
90
- // attempts re-pick from a panel that has already rendered once, so
125
+ // Only the first attempt waits out a slow first render; later
126
+ // attempts re-pick from a page that has already rendered once, so
91
127
  // they stay short -- three attempts at the full budget would outlast
92
- // the 12-minute test timeout and turn a dropped click into a timeout
128
+ // the 12-minute test timeout and turn a dropped pick into a timeout
93
129
  // with no retry spent.
94
130
  const renderTimeout = attempt === 1 ? 120000 : 15000;
95
- await (0, test_1.expect)(filterLink).toBeVisible({ timeout: renderTimeout });
96
- await filterLink.click({ timeout: renderTimeout });
131
+ await this.openFilterControls();
132
+ await (0, test_1.expect)(filterOption).toBeVisible({ timeout: renderTimeout });
133
+ await filterOption.click({ timeout: renderTimeout });
97
134
  const applied = await this.page
98
135
  .waitForURL(TaskPanelPage.appliedFilterUrl(option), { timeout: 15000 })
99
136
  .then(() => true)
@@ -104,30 +141,47 @@ class TaskPanelPage {
104
141
  lastError = new Error(`Filter "${option}" was picked but the URL never carried it`);
105
142
  }
106
143
  catch (error) {
107
- // Without this catch, a thrown assertion (the expand control or the
108
- // filter link never appearing) propagated straight out of filterBy()
109
- // on attempt 1, so attempts 2 and 3 -- and their shorter, faster-
110
- // failing budget -- could never run. maxAttempts was dead code for
111
- // every failure mode except a click that landed but didn't navigate.
144
+ // Without this catch, a thrown assertion (the control never appearing,
145
+ // or the option never rendering) propagated straight out of filterBy()
146
+ // on attempt 1, so attempts 2 and 3 -- and their shorter,
147
+ // faster-failing budget -- could never run. maxAttempts was dead code.
112
148
  lastError = error;
113
149
  }
114
150
  if (attempt < maxAttempts) {
115
151
  console.warn(`Attempt ${attempt} did not apply the "${option}" filter. Retrying...`);
116
152
  }
153
+ // This attempt did not land, which leaves the Design System menu in
154
+ // whichever state Radix last put it. Clicking that trigger again
155
+ // toggles, so on a still-open menu the next attempt would *close* it and
156
+ // then burn its whole budget waiting for an option that is gone. Escape
157
+ // resets it, and is a no-op on both an already-closed menu and the
158
+ // Carbon panel (whose expanded state openFilterControls() probes for).
159
+ // It sits outside the catch so a dead page surfaces as itself rather
160
+ // than replacing the real failure.
161
+ await this.page.keyboard.press('Escape');
117
162
  }
118
163
  throw new Error(`Filter "${option}" was not applied after ${maxAttempts} attempts: ${lastError}`);
119
164
  }
120
- // The filter panel renders either the collapsed rail (which carries the
165
+ // The Carbon panel renders either the collapsed rail (which carries the
121
166
  // expand control) or the expanded nav -- never both -- and picking a filter
122
- // leaves it expanded, so a retry must not click "expand" a second time.
123
- async expandFilterPanel() {
124
- const alreadyExpanded = await this.collapseFilter
167
+ // leaves it expanded, so a retry must not click "expand" a second time. The
168
+ // Design System dropdown has the opposite need: it closes on select, so it
169
+ // has to be re-opened every attempt. Probing for the Carbon collapse control
170
+ // first tells the two apart without branching on a version flag.
171
+ async openFilterControls() {
172
+ const alreadyExpanded = await this.filterPanelCollapseButton
125
173
  .waitFor({ state: 'visible', timeout: 2000 })
126
174
  .then(() => true)
127
175
  .catch(() => false);
128
- if (!alreadyExpanded) {
129
- await this.filterOptions.click({ timeout: 45000 });
176
+ if (alreadyExpanded) {
177
+ return;
130
178
  }
179
+ // click() auto-waits for the control, so this budget doubles as the wait
180
+ // for a slow first render of whichever shell is served.
181
+ await this.filterPanelExpandButton
182
+ .or(this.filterSelectButton)
183
+ .first()
184
+ .click({ timeout: 60000 });
131
185
  }
132
186
  // Tasklist keeps the active filter in the `filter` search param. "All open"
133
187
  // is the route's default and is stripped from the URL, so it is asserted as
@@ -144,8 +198,28 @@ class TaskPanelPage {
144
198
  ? /\/tasklist(?:\/[^?]*)?(?:\?(?!.*\bfilter=).*)?$/
145
199
  : new RegExp(`[?&]filter=${param}(?:&|$)`);
146
200
  }
201
+ // Where each shell shows which filter is currently applied: the Carbon
202
+ // panel renders an `<h1>` naming it, while the Design System dropdown has no
203
+ // heading and instead carries the label on the trigger itself.
204
+ activeFilter(option) {
205
+ return this.page
206
+ .getByRole('heading', { name: option })
207
+ .or(this.filterSelectButton.getByText(option, { exact: true }))
208
+ .first();
209
+ }
210
+ // Picking a filter leaves the Carbon panel expanded, and it floats over the
211
+ // task list -- callers that go straight to a task row would be clicking
212
+ // through the overlay. The Design System dropdown closes itself on select
213
+ // and has no collapse control at all, so this probes rather than clicking
214
+ // unconditionally: on that shell it is a no-op the callers can keep calling.
147
215
  async clickCollapseFilter() {
148
- await this.collapseFilter.click({ timeout: 45000 });
216
+ const expanded = await this.filterPanelCollapseButton
217
+ .waitFor({ state: 'visible', timeout: 2000 })
218
+ .then(() => true)
219
+ .catch(() => false);
220
+ if (expanded) {
221
+ await this.filterPanelCollapseButton.click({ timeout: 45000 });
222
+ }
149
223
  }
150
224
  async scrollToLastTask(name) {
151
225
  await this.page.getByText(name).last().scrollIntoViewIfNeeded();
@@ -84,7 +84,7 @@ SM_8_10_1.test.describe('HTO User Flow Tests', () => {
84
84
  await (0, UtilitiesPage_1.completeTaskWithRetry)(page, taskPanelPage, taskDetailsPage, `${taskName}1`, 'low');
85
85
  await taskPanelPage.filterBy('Completed');
86
86
  await taskPanelPage.clickCollapseFilter();
87
- await (0, test_1.expect)(page.getByRole('heading', { name: 'completed' })).toBeVisible({
87
+ await (0, test_1.expect)(taskPanelPage.activeFilter('Completed')).toBeVisible({
88
88
  timeout: 45000,
89
89
  });
90
90
  await page.reload();
@@ -50,7 +50,7 @@ if (process.env.IS_MIGRATION === 'true') {
50
50
  await navigationPage.goToTasklist();
51
51
  await taskPanelPage.filterBy('Completed');
52
52
  await taskPanelPage.clickCollapseFilter();
53
- await (0, test_1.expect)(page.getByRole('heading', { name: 'completed' })).toBeVisible({
53
+ await (0, test_1.expect)(taskPanelPage.activeFilter('Completed')).toBeVisible({
54
54
  timeout: 45000,
55
55
  });
56
56
  await (0, test_1.expect)(page.getByText(processName).first()).toBeVisible({
@@ -691,14 +691,14 @@ if (node_process_1.default.env.IS_RBA === 'true') {
691
691
  const secondProcessTaskCount = await taskPanelPage.taskCount(`${processName}2`);
692
692
  await taskPanelPage.clickProcessesTab();
693
693
  await taskProcessesPage.startProcess(`${processName}1`);
694
- await (0, test_1.expect)(taskPanelPage.filterOptions).toBeVisible({
694
+ await (0, test_1.expect)(taskPanelPage.filterControls).toBeVisible({
695
695
  timeout: 120000,
696
696
  });
697
697
  const updatedFirstProcessTaskCount = await taskPanelPage.taskCount(`${processName}1`);
698
698
  (0, test_1.expect)(updatedFirstProcessTaskCount).toBe(firstProcessTaskCount + 1);
699
699
  await taskPanelPage.clickProcessesTab();
700
700
  await taskProcessesPage.startProcess(`${processName}2`);
701
- await (0, test_1.expect)(taskPanelPage.filterOptions).toBeVisible({
701
+ await (0, test_1.expect)(taskPanelPage.filterControls).toBeVisible({
702
702
  timeout: 120000,
703
703
  });
704
704
  const updatedSecondProcessTaskCount = await taskPanelPage.taskCount(`${processName}2`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1195",
3
+ "version": "0.0.1197",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",