@camunda/e2e-test-suite 0.0.1146 → 0.0.1148
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/test-setup.js +60 -7
- package/dist/utils/modelerCluster.d.ts +11 -1
- package/dist/utils/modelerCluster.js +53 -10
- package/package.json +1 -1
package/dist/test-setup.js
CHANGED
|
@@ -16,7 +16,64 @@ async function captureScreenshot(page, testInfo) {
|
|
|
16
16
|
if (testInfo.status === 'passed' || testInfo.status === 'skipped') {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
// Capture every open tab, not just the one handed in. Flows that reach
|
|
20
|
+
// Operate/Tasklist through a link (openAppTabWithRetry) assert against a
|
|
21
|
+
// second Page, so capturing only `page` attaches a picture of the tab the
|
|
22
|
+
// test had already left: a doc-handling failure asserting the Operate
|
|
23
|
+
// completed-icon attached the Modeler canvas instead, which is unusable for
|
|
24
|
+
// triage.
|
|
25
|
+
let targets = [page];
|
|
26
|
+
try {
|
|
27
|
+
targets = [
|
|
28
|
+
page,
|
|
29
|
+
...page
|
|
30
|
+
.context()
|
|
31
|
+
.pages()
|
|
32
|
+
.filter((candidate) => candidate !== page && !candidate.isClosed()),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
catch (contextError) {
|
|
36
|
+
console.warn(`captureScreenshot: could not enumerate context pages (${contextError}). ` +
|
|
37
|
+
'Capturing the handed-in page only.');
|
|
38
|
+
}
|
|
39
|
+
let extraTabIndex = 1;
|
|
40
|
+
for (const target of targets) {
|
|
41
|
+
const screenshotPath = `test-results/screenshots/screenshot-${(0, crypto_1.randomUUID)()}.png`;
|
|
42
|
+
const isPrimaryTab = target === page;
|
|
43
|
+
let captured = false;
|
|
44
|
+
if (isPrimaryTab) {
|
|
45
|
+
captured = await screenshotWithViewportFallback(target, screenshotPath);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
try {
|
|
49
|
+
await target.screenshot({ path: screenshotPath, timeout: 30000 });
|
|
50
|
+
captured = true;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.warn(`captureScreenshot: extra-tab viewport capture failed (${error}). ` +
|
|
54
|
+
'Continuing without a screenshot; the test verdict is unaffected.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!captured) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const attachmentName = isPrimaryTab
|
|
61
|
+
? 'screenshot'
|
|
62
|
+
: `screenshot-tab-${extraTabIndex++}`;
|
|
63
|
+
try {
|
|
64
|
+
await testInfo.attach(attachmentName, {
|
|
65
|
+
path: screenshotPath,
|
|
66
|
+
contentType: 'image/png',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.warn(`captureScreenshot: failed to attach ${attachmentName} (${error}). ` +
|
|
71
|
+
'Continuing without the attachment; the test verdict is unaffected.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.captureScreenshot = captureScreenshot;
|
|
76
|
+
async function screenshotWithViewportFallback(page, screenshotPath) {
|
|
20
77
|
try {
|
|
21
78
|
await page.screenshot({
|
|
22
79
|
path: screenshotPath,
|
|
@@ -33,15 +90,11 @@ async function captureScreenshot(page, testInfo) {
|
|
|
33
90
|
catch (viewportError) {
|
|
34
91
|
console.warn(`captureScreenshot: viewport capture also failed (${viewportError}). ` +
|
|
35
92
|
'Continuing without a screenshot; the test verdict is unaffected.');
|
|
36
|
-
return;
|
|
93
|
+
return false;
|
|
37
94
|
}
|
|
38
95
|
}
|
|
39
|
-
|
|
40
|
-
path: screenshotPath,
|
|
41
|
-
contentType: 'image/png',
|
|
42
|
-
});
|
|
96
|
+
return true;
|
|
43
97
|
}
|
|
44
|
-
exports.captureScreenshot = captureScreenshot;
|
|
45
98
|
async function captureFailureVideo(page, testInfo) {
|
|
46
99
|
// Same policy as captureScreenshot above: skip on passed/skipped rather
|
|
47
100
|
// than requiring literally 'failed', so timed-out/interrupted tests keep
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { Locator } from '@playwright/test';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Points a Web Modeler deploy dialog at the cluster named by MODELER_CLUSTER_NAME.
|
|
4
4
|
*
|
|
5
5
|
* No-op when the variable is unset, which is every single-cluster environment. In a topology one
|
|
6
6
|
* Hub Web Modeler serves several orchestration clusters and preselects one of them, so a test that
|
|
7
7
|
* does not choose deploys somewhere other than the release under test.
|
|
8
|
+
*
|
|
9
|
+
* Two dialog generations are in the field:
|
|
10
|
+
*
|
|
11
|
+
* - The legacy one offers a per-deploy cluster combobox labelled "Select a cluster".
|
|
12
|
+
* - Hub's stage-based one offers #cluster-selection-dropdown -- labelled "Stage", aria-label
|
|
13
|
+
* "Select a stage" -- which lists only the clusters bound to the process application's stages
|
|
14
|
+
* and arrives with the one ProcessApplicationPage.ensureStageCluster bound already selected.
|
|
15
|
+
*
|
|
16
|
+
* Hence keying the trigger on its id rather than its accessible name: the id says what the
|
|
17
|
+
* control is for and has survived the rename, the label is prose and did not.
|
|
8
18
|
*/
|
|
9
19
|
export declare function selectModelerCluster(dialog: Locator): Promise<void>;
|
|
@@ -3,28 +3,71 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.selectModelerCluster = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Points a Web Modeler deploy dialog at the cluster named by MODELER_CLUSTER_NAME.
|
|
7
7
|
*
|
|
8
8
|
* No-op when the variable is unset, which is every single-cluster environment. In a topology one
|
|
9
9
|
* Hub Web Modeler serves several orchestration clusters and preselects one of them, so a test that
|
|
10
10
|
* does not choose deploys somewhere other than the release under test.
|
|
11
|
+
*
|
|
12
|
+
* Two dialog generations are in the field:
|
|
13
|
+
*
|
|
14
|
+
* - The legacy one offers a per-deploy cluster combobox labelled "Select a cluster".
|
|
15
|
+
* - Hub's stage-based one offers #cluster-selection-dropdown -- labelled "Stage", aria-label
|
|
16
|
+
* "Select a stage" -- which lists only the clusters bound to the process application's stages
|
|
17
|
+
* and arrives with the one ProcessApplicationPage.ensureStageCluster bound already selected.
|
|
18
|
+
*
|
|
19
|
+
* Hence keying the trigger on its id rather than its accessible name: the id says what the
|
|
20
|
+
* control is for and has survived the rename, the label is prose and did not.
|
|
11
21
|
*/
|
|
12
22
|
async function selectModelerCluster(dialog) {
|
|
13
23
|
const modelerClusterName = process.env.MODELER_CLUSTER_NAME;
|
|
14
24
|
if (!modelerClusterName) {
|
|
15
25
|
return;
|
|
16
26
|
}
|
|
17
|
-
const
|
|
27
|
+
const page = dialog.page();
|
|
28
|
+
const stageSelect = dialog.locator('#cluster-selection-dropdown');
|
|
29
|
+
const legacySelect = dialog.getByRole('combobox', {
|
|
18
30
|
name: 'Select a cluster',
|
|
19
31
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
32
|
+
// Wait on the union so whichever generation this dialog is settles the wait, instead of paying a
|
|
33
|
+
// full timeout probing the other one first.
|
|
34
|
+
const appeared = await stageSelect
|
|
35
|
+
.or(legacySelect)
|
|
36
|
+
.first()
|
|
37
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
38
|
+
.then(() => true)
|
|
39
|
+
.catch(() => false);
|
|
40
|
+
if (!appeared) {
|
|
41
|
+
throw new Error(`The deploy dialog offers no cluster selector, so "${modelerClusterName}" cannot be ` +
|
|
42
|
+
'chosen: neither #cluster-selection-dropdown (stage-based dialog) nor a "Select a ' +
|
|
43
|
+
'cluster" combobox (legacy dialog) became visible.');
|
|
44
|
+
}
|
|
45
|
+
const clusterSelect = (await stageSelect.isVisible().catch(() => false))
|
|
46
|
+
? stageSelect
|
|
47
|
+
: legacySelect;
|
|
48
|
+
// The stage dialog arrives with the bound cluster already selected, so the usual outcome is that
|
|
49
|
+
// nothing is clicked at all. That is not just a saved click: the option list unmounts on the
|
|
50
|
+
// click that lands, so every open is a chance to lose that race.
|
|
51
|
+
const selected = (await clusterSelect.textContent()) ?? '';
|
|
52
|
+
if (!selected.includes(modelerClusterName)) {
|
|
53
|
+
await clusterSelect.click();
|
|
54
|
+
const escapedClusterName = modelerClusterName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
55
|
+
// A stage option's accessible name is "<stage><cluster> (<generation>)" -- the stage and the
|
|
56
|
+
// cluster sit in adjacent spans with no whitespace between them, so it reads
|
|
57
|
+
// "DevelopmentOrchestration A (8.10.0-alpha3)" and the cluster name is not at a word
|
|
58
|
+
// boundary. A legacy option is just "<cluster> (<status>)". Anchoring the start therefore
|
|
59
|
+
// matches neither reliably; the trailing " (" is what keeps the match honest, and is enough
|
|
60
|
+
// on its own to tell "Orch" from "Orchestration A".
|
|
61
|
+
const optionName = new RegExp(`${escapedClusterName}(?:\\s*\\(|$)`);
|
|
62
|
+
// The stage select is a Radix UI Select: its listbox is portalled to document.body, OUTSIDE
|
|
63
|
+
// the dialog element, so a dialog-scoped option query never resolves even while the option is
|
|
64
|
+
// visible on screen. Query at page scope; the trigger above stays dialog-scoped because it
|
|
65
|
+
// renders inside the dialog.
|
|
66
|
+
await page
|
|
67
|
+
.getByRole('option', { name: optionName })
|
|
68
|
+
.first()
|
|
69
|
+
.click({ timeout: 30000 });
|
|
70
|
+
}
|
|
28
71
|
await (0, test_1.expect)(clusterSelect).toContainText(modelerClusterName);
|
|
29
72
|
}
|
|
30
73
|
exports.selectModelerCluster = selectModelerCluster;
|