@grafana/plugin-e2e 0.1.0 → 0.2.1-canary.616.211cb1f.0
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/api.d.ts +96 -0
- package/dist/api.js +10 -0
- package/dist/e2e-selectors/resolver.d.ts +7 -0
- package/dist/e2e-selectors/resolver.js +12 -0
- package/dist/e2e-selectors/resolver.test.js +3 -0
- package/dist/e2e-selectors/types.d.ts +8 -0
- package/dist/e2e-selectors/versioned/components.d.ts +36 -0
- package/dist/e2e-selectors/versioned/components.js +41 -0
- package/dist/e2e-selectors/versioned/pages.d.ts +18 -0
- package/dist/e2e-selectors/versioned/pages.js +18 -0
- package/dist/models/AnnotationEditPage.d.ts +4 -0
- package/dist/models/AnnotationEditPage.js +5 -0
- package/dist/models/AnnotationPage.js +3 -0
- package/dist/models/DataSourceConfigPage.d.ts +10 -0
- package/dist/models/DataSourceConfigPage.js +10 -0
- package/dist/models/DataSourcePicker.js +2 -0
- package/dist/models/ExplorePage.js +1 -0
- package/dist/models/GrafanaPage.d.ts +31 -0
- package/dist/models/GrafanaPage.js +32 -0
- package/dist/models/PanelEditPage.d.ts +2 -0
- package/dist/models/PanelEditPage.js +19 -0
- package/dist/models/TimeRange.js +2 -0
- package/dist/models/VariableEditPage.d.ts +7 -0
- package/dist/models/VariableEditPage.js +9 -0
- package/dist/selectorEngine.d.ts +3 -0
- package/dist/selectorEngine.js +5 -0
- package/dist/types.d.ts +117 -0
- package/package.json +2 -2
package/dist/api.d.ts
CHANGED
|
@@ -6,17 +6,113 @@ export type PluginOptions = {
|
|
|
6
6
|
selectorRegistration: void;
|
|
7
7
|
};
|
|
8
8
|
export type PluginFixture = {
|
|
9
|
+
/**
|
|
10
|
+
* The current Grafana version.
|
|
11
|
+
*
|
|
12
|
+
* If a GRAFANA_VERSION environment variable is set, this will be used. Otherwise,
|
|
13
|
+
* the version will be picked from window.grafanaBootData.settings.buildInfo.version.
|
|
14
|
+
*/
|
|
9
15
|
grafanaVersion: string;
|
|
16
|
+
/**
|
|
17
|
+
* The E2E selectors to use for the current version of Grafana
|
|
18
|
+
*/
|
|
10
19
|
selectors: E2ESelectors;
|
|
20
|
+
/**
|
|
21
|
+
* Isolated {@link DashboardPage} instance for each test.
|
|
22
|
+
*
|
|
23
|
+
* Navigates to a new dashboard page and adds a new panel.
|
|
24
|
+
*
|
|
25
|
+
* Use {@link PanelEditPage.setVisualization} to change the visualization
|
|
26
|
+
* Use {@link PanelEditPage.datasource.set} to change the datasource
|
|
27
|
+
* Use {@link PanelEditPage.getQueryEditorEditorRow} to retrieve the query
|
|
28
|
+
* editor row locator for a given query refId
|
|
29
|
+
*/
|
|
11
30
|
newDashboardPage: DashboardPage;
|
|
31
|
+
/**
|
|
32
|
+
* Isolated {@link PanelEditPage} instance for each test.
|
|
33
|
+
*
|
|
34
|
+
* Navigates to a new dashboard page and adds a new panel.
|
|
35
|
+
*
|
|
36
|
+
* Use {@link PanelEditPage.setVisualization} to change the visualization
|
|
37
|
+
* Use {@link PanelEditPage.datasource.set} to change the datasource
|
|
38
|
+
* Use {@link ExplorePage.getQueryEditorEditorRow} to retrieve the query
|
|
39
|
+
* editor row locator for a given query refId
|
|
40
|
+
*/
|
|
12
41
|
panelEditPage: PanelEditPage;
|
|
42
|
+
/**
|
|
43
|
+
* Isolated {@link VariableEditPage} instance for each test.
|
|
44
|
+
*
|
|
45
|
+
* Navigates to a new dashboard page and adds a new variable.
|
|
46
|
+
*
|
|
47
|
+
* Use {@link VariableEditPage.setVariableType} to change the variable type
|
|
48
|
+
*/
|
|
13
49
|
variableEditPage: VariableEditPage;
|
|
50
|
+
/**
|
|
51
|
+
* Isolated {@link AnnotationEditPage} instance for each test.
|
|
52
|
+
*
|
|
53
|
+
* Navigates to a new dashboard page and adds a new annotation.
|
|
54
|
+
*
|
|
55
|
+
* Use {@link AnnotationEditPage.datasource.set} to change the datasource
|
|
56
|
+
*/
|
|
14
57
|
annotationEditPage: AnnotationEditPage;
|
|
58
|
+
/**
|
|
59
|
+
* Isolated {@link ExplorePage} instance for each test.
|
|
60
|
+
*
|
|
61
|
+
* Navigates to a the explore page.
|
|
62
|
+
*
|
|
63
|
+
* Use {@link ExplorePage.datasource.set} to change the datasource
|
|
64
|
+
* Use {@link ExplorePage.getQueryEditorEditorRow} to retrieve the query editor
|
|
65
|
+
* row locator for a given query refId
|
|
66
|
+
*/
|
|
15
67
|
explorePage: ExplorePage;
|
|
68
|
+
/**
|
|
69
|
+
* Fixture command that will create an isolated DataSourceConfigPage instance for a given data source type.
|
|
70
|
+
*
|
|
71
|
+
* The data source config page cannot be navigated to without a data source uid, so this fixture will create a new
|
|
72
|
+
* data source using the Grafana API, create a new DataSourceConfigPage instance and navigate to the page.
|
|
73
|
+
*/
|
|
16
74
|
createDataSourceConfigPage: (args: CreateDataSourcePageArgs) => Promise<DataSourceConfigPage>;
|
|
75
|
+
/**
|
|
76
|
+
* Fixture command that creates a data source via the Grafana API.
|
|
77
|
+
*
|
|
78
|
+
* If you have tests that depend on the the existance of a data source,
|
|
79
|
+
* you may use this command in a setup project. Read more about setup projects
|
|
80
|
+
* here: https://playwright.dev/docs/auth#basic-shared-account-in-all-tests
|
|
81
|
+
*/
|
|
17
82
|
createDataSource: (args: CreateDataSourceArgs) => Promise<DataSource>;
|
|
83
|
+
/**
|
|
84
|
+
* Fixture command that login to Grafana using the Grafana API.
|
|
85
|
+
* If the same credentials should be used in every test,
|
|
86
|
+
* invoke this fixture in a setup project.
|
|
87
|
+
* See https://playwright.dev/docs/auth#basic-shared-account-in-all-tests
|
|
88
|
+
*
|
|
89
|
+
* If no credentials are provided, the default admin/admin credentials will be used.
|
|
90
|
+
*
|
|
91
|
+
* The default credentials can be overridden in the playwright.config.ts file:
|
|
92
|
+
* eg.
|
|
93
|
+
* export default defineConfig({
|
|
94
|
+
use: {
|
|
95
|
+
httpCredentials: {
|
|
96
|
+
username: 'user',
|
|
97
|
+
password: 'pass',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
*
|
|
102
|
+
* To override credentials in a single test:
|
|
103
|
+
* test.use({ httpCredentials: { username: 'admin', password: 'admin' } });
|
|
104
|
+
* To avoid authentication in a single test:
|
|
105
|
+
* test.use({ storageState: { cookies: [], origins: [] } });
|
|
106
|
+
*/
|
|
18
107
|
login: () => Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Fixture command that reads a the yaml file for a provisioned dashboard
|
|
110
|
+
* or data source and returns it as json.
|
|
111
|
+
*/
|
|
19
112
|
readProvision<T = any>(args: ReadProvisionArgs): Promise<T>;
|
|
113
|
+
/**
|
|
114
|
+
* Function that checks if a feature toggle is enabled. Only works for frontend feature toggles.
|
|
115
|
+
*/
|
|
20
116
|
isFeatureToggleEnabled<T = object>(featureToggle: keyof T): Promise<boolean>;
|
|
21
117
|
};
|
|
22
118
|
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & PluginFixture & PluginOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
package/dist/api.js
CHANGED
|
@@ -8,8 +8,18 @@ const test_1 = require("@playwright/test");
|
|
|
8
8
|
const fixtures_1 = __importDefault(require("./fixtures"));
|
|
9
9
|
const matchers_1 = __importDefault(require("./matchers"));
|
|
10
10
|
const selectorEngine_1 = require("./selectorEngine");
|
|
11
|
+
// extend Playwright with Grafana plugin specific fixtures
|
|
11
12
|
exports.test = test_1.test.extend(fixtures_1.default);
|
|
12
13
|
exports.expect = test_1.expect.extend(matchers_1.default);
|
|
14
|
+
/** Register a custom selector engine that resolves locators for Grafana E2E selectors
|
|
15
|
+
*
|
|
16
|
+
* The same functionality is available in the {@link GrafanaPage.getByTestIdOrAriaLabel} method. However,
|
|
17
|
+
* by registering the selector engine, one can resolve locators by Grafana E2E selectors also within a locator.
|
|
18
|
+
*
|
|
19
|
+
* Example:
|
|
20
|
+
* const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); // returns a locator
|
|
21
|
+
* queryEditorRow.locator(`selector=${selectors.components.TimePicker.openButton}`).click();
|
|
22
|
+
* */
|
|
13
23
|
test_1.selectors.register('selector', selectorEngine_1.grafanaE2ESelectorEngine);
|
|
14
24
|
var test_2 = require("@playwright/test");
|
|
15
25
|
Object.defineProperty(exports, "selectors", { enumerable: true, get: function () { return test_2.selectors; } });
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import { E2ESelectors } from './types';
|
|
2
2
|
import { VersionedSelectors } from './versioned/types';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves selectors based on the Grafana version
|
|
5
|
+
*
|
|
6
|
+
* If the selector has multiple versions, the last version that is less
|
|
7
|
+
* than or equal to the Grafana version will be returned.
|
|
8
|
+
* If the selector doesn't have a version, it will be returned as is.
|
|
9
|
+
*/
|
|
3
10
|
export declare const resolveSelectors: (versionedSelectors: VersionedSelectors, grafanaVersion: string) => E2ESelectors;
|
|
@@ -6,12 +6,15 @@ const processSelectors = (selectors, versionedSelectors, grafanaVersion) => {
|
|
|
6
6
|
const keys = Object.keys(versionedSelectors);
|
|
7
7
|
for (let index = 0; index < keys.length; index++) {
|
|
8
8
|
const key = keys[index];
|
|
9
|
+
// @ts-ignore
|
|
9
10
|
const value = versionedSelectors[key];
|
|
10
11
|
if (typeof value === 'object' && Object.keys(value).length > 0 && !semver.valid(Object.keys(value)[0])) {
|
|
12
|
+
// @ts-ignore
|
|
11
13
|
selectors[key] = processSelectors({}, value, grafanaVersion);
|
|
12
14
|
}
|
|
13
15
|
else {
|
|
14
16
|
if (typeof value === 'object' && Object.keys(value).length > 0 && semver.valid(Object.keys(value)[0])) {
|
|
17
|
+
// @ts-ignore
|
|
15
18
|
const sorted = Object.keys(value).sort(semver.rcompare);
|
|
16
19
|
let validVersion = sorted[0];
|
|
17
20
|
for (let index = 0; index < sorted.length; index++) {
|
|
@@ -21,9 +24,11 @@ const processSelectors = (selectors, versionedSelectors, grafanaVersion) => {
|
|
|
21
24
|
break;
|
|
22
25
|
}
|
|
23
26
|
}
|
|
27
|
+
// @ts-ignore
|
|
24
28
|
selectors[key] = value[validVersion];
|
|
25
29
|
}
|
|
26
30
|
else {
|
|
31
|
+
// @ts-ignore
|
|
27
32
|
selectors[key] = value;
|
|
28
33
|
}
|
|
29
34
|
}
|
|
@@ -31,6 +36,13 @@ const processSelectors = (selectors, versionedSelectors, grafanaVersion) => {
|
|
|
31
36
|
}
|
|
32
37
|
return selectors;
|
|
33
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Resolves selectors based on the Grafana version
|
|
41
|
+
*
|
|
42
|
+
* If the selector has multiple versions, the last version that is less
|
|
43
|
+
* than or equal to the Grafana version will be returned.
|
|
44
|
+
* If the selector doesn't have a version, it will be returned as is.
|
|
45
|
+
*/
|
|
34
46
|
const resolveSelectors = (versionedSelectors, grafanaVersion) => {
|
|
35
47
|
const selectors = {};
|
|
36
48
|
return processSelectors(selectors, versionedSelectors, grafanaVersion.replace(/\-.*/, ''));
|
|
@@ -25,10 +25,13 @@ describe('resolveSelectors', () => {
|
|
|
25
25
|
'10.3.0': 'data-testid Code editor container',
|
|
26
26
|
[constants_1.MIN_GRAFANA_VERSION]: 'Code editor container',
|
|
27
27
|
};
|
|
28
|
+
// semver great than
|
|
28
29
|
let selectors = (0, resolver_1.resolveSelectors)(versionedSelectors, '10.4.0');
|
|
29
30
|
expect(selectors.components.CodeEditor.container).toBe('data-testid Code editor container');
|
|
31
|
+
// semver equals to
|
|
30
32
|
selectors = (0, resolver_1.resolveSelectors)(versionedSelectors, '10.3.0');
|
|
31
33
|
expect(selectors.components.CodeEditor.container).toBe('data-testid Code editor container');
|
|
34
|
+
// semver equals to when using pre-release
|
|
32
35
|
selectors = (0, resolver_1.resolveSelectors)(versionedSelectors, '10.3.0-pre');
|
|
33
36
|
expect(selectors.components.CodeEditor.container).toBe('data-testid Code editor container');
|
|
34
37
|
selectors = (0, resolver_1.resolveSelectors)(versionedSelectors, '9.2.0');
|
|
@@ -191,6 +191,9 @@ export type Components = {
|
|
|
191
191
|
content: string;
|
|
192
192
|
};
|
|
193
193
|
Alert: {
|
|
194
|
+
/**
|
|
195
|
+
* @deprecated use alertV2 from Grafana 8.3 instead
|
|
196
|
+
*/
|
|
194
197
|
alert: (severity: string) => string;
|
|
195
198
|
alertV2: (severity: string) => string;
|
|
196
199
|
};
|
|
@@ -257,6 +260,7 @@ export type Components = {
|
|
|
257
260
|
OptionsGroup: {
|
|
258
261
|
group: (title?: string) => string;
|
|
259
262
|
toggle: (title?: string) => string;
|
|
263
|
+
groupTitle: string;
|
|
260
264
|
};
|
|
261
265
|
PluginVisualization: {
|
|
262
266
|
current: string;
|
|
@@ -402,6 +406,7 @@ export type Pages = {
|
|
|
402
406
|
};
|
|
403
407
|
AddDataSource: {
|
|
404
408
|
url: string;
|
|
409
|
+
/** @deprecated Use dataSourcePluginsV2 */
|
|
405
410
|
dataSourcePlugins: (pluginName: string) => string;
|
|
406
411
|
dataSourcePluginsV2: (pluginName: string) => string;
|
|
407
412
|
};
|
|
@@ -472,6 +477,9 @@ export type Pages = {
|
|
|
472
477
|
};
|
|
473
478
|
List: {
|
|
474
479
|
url: (uid: string) => string;
|
|
480
|
+
/**
|
|
481
|
+
* @deprecated use addAnnotationCTAV2 from Grafana 8.3 instead
|
|
482
|
+
*/
|
|
475
483
|
addAnnotationCTA: string;
|
|
476
484
|
addAnnotationCTAV2: string;
|
|
477
485
|
};
|
|
@@ -96,6 +96,9 @@ export declare const versionedComponents: {
|
|
|
96
96
|
};
|
|
97
97
|
};
|
|
98
98
|
BarGauge: {
|
|
99
|
+
/**
|
|
100
|
+
* @deprecated use valueV2 from Grafana 8.3 instead
|
|
101
|
+
*/
|
|
99
102
|
value: string;
|
|
100
103
|
valueV2: string;
|
|
101
104
|
};
|
|
@@ -173,7 +176,13 @@ export declare const versionedComponents: {
|
|
|
173
176
|
active: () => string;
|
|
174
177
|
};
|
|
175
178
|
RefreshPicker: {
|
|
179
|
+
/**
|
|
180
|
+
* @deprecated use runButtonV2 from Grafana 8.3 instead
|
|
181
|
+
*/
|
|
176
182
|
runButton: string;
|
|
183
|
+
/**
|
|
184
|
+
* @deprecated use intervalButtonV2 from Grafana 8.3 instead
|
|
185
|
+
*/
|
|
177
186
|
intervalButton: string;
|
|
178
187
|
runButtonV2: string;
|
|
179
188
|
intervalButtonV2: string;
|
|
@@ -199,6 +208,9 @@ export declare const versionedComponents: {
|
|
|
199
208
|
content: string;
|
|
200
209
|
};
|
|
201
210
|
Alert: {
|
|
211
|
+
/**
|
|
212
|
+
* @deprecated use alertV2 from Grafana 8.3 instead
|
|
213
|
+
*/
|
|
202
214
|
alert: (severity: string) => string;
|
|
203
215
|
alertV2: (severity: string) => string;
|
|
204
216
|
};
|
|
@@ -276,6 +288,9 @@ export declare const versionedComponents: {
|
|
|
276
288
|
OptionsGroup: {
|
|
277
289
|
group: (title?: string) => string;
|
|
278
290
|
toggle: (title?: string) => string;
|
|
291
|
+
groupTitle: {
|
|
292
|
+
"8.0.0": string;
|
|
293
|
+
};
|
|
279
294
|
};
|
|
280
295
|
PluginVisualization: {
|
|
281
296
|
item: (title: string) => string;
|
|
@@ -293,6 +308,9 @@ export declare const versionedComponents: {
|
|
|
293
308
|
content: string;
|
|
294
309
|
};
|
|
295
310
|
FolderPicker: {
|
|
311
|
+
/**
|
|
312
|
+
* @deprecated use containerV2 from Grafana 8.3 instead
|
|
313
|
+
*/
|
|
296
314
|
container: string;
|
|
297
315
|
containerV2: string;
|
|
298
316
|
input: string;
|
|
@@ -305,14 +323,23 @@ export declare const versionedComponents: {
|
|
|
305
323
|
'10.0.0': string;
|
|
306
324
|
'8.3.0': string;
|
|
307
325
|
};
|
|
326
|
+
/**
|
|
327
|
+
* @deprecated use inputV2 instead
|
|
328
|
+
*/
|
|
308
329
|
input: () => string;
|
|
309
330
|
inputV2: string;
|
|
310
331
|
};
|
|
311
332
|
TimeZonePicker: {
|
|
333
|
+
/**
|
|
334
|
+
* @deprecated use TimeZonePicker.containerV2 from Grafana 8.3 instead
|
|
335
|
+
*/
|
|
312
336
|
container: string;
|
|
313
337
|
containerV2: string;
|
|
314
338
|
};
|
|
315
339
|
WeekStartPicker: {
|
|
340
|
+
/**
|
|
341
|
+
* @deprecated use WeekStartPicker.containerV2 from Grafana 8.3 instead
|
|
342
|
+
*/
|
|
316
343
|
container: string;
|
|
317
344
|
containerV2: string;
|
|
318
345
|
placeholder: string;
|
|
@@ -334,8 +361,14 @@ export declare const versionedComponents: {
|
|
|
334
361
|
select: (name: string) => string;
|
|
335
362
|
};
|
|
336
363
|
Search: {
|
|
364
|
+
/**
|
|
365
|
+
* @deprecated use sectionV2 from Grafana 8.3 instead
|
|
366
|
+
*/
|
|
337
367
|
section: string;
|
|
338
368
|
sectionV2: string;
|
|
369
|
+
/**
|
|
370
|
+
* @deprecated use itemsV2 from Grafana 8.3 instead
|
|
371
|
+
*/
|
|
339
372
|
items: string;
|
|
340
373
|
itemsV2: string;
|
|
341
374
|
cards: string;
|
|
@@ -355,6 +388,9 @@ export declare const versionedComponents: {
|
|
|
355
388
|
icon: string;
|
|
356
389
|
};
|
|
357
390
|
CallToActionCard: {
|
|
391
|
+
/**
|
|
392
|
+
* @deprecated use buttonV2 from Grafana 8.3 instead
|
|
393
|
+
*/
|
|
358
394
|
button: (name: string) => string;
|
|
359
395
|
buttonV2: (name: string) => string;
|
|
360
396
|
};
|
|
@@ -5,6 +5,7 @@ const constants_1 = require("./constants");
|
|
|
5
5
|
exports.versionedComponents = {
|
|
6
6
|
Breadcrumbs: {
|
|
7
7
|
breadcrumb: {
|
|
8
|
+
// did not exist prior to 9.4.0
|
|
8
9
|
'9.4.0': (title) => `data-testid ${title} breadcrumb`,
|
|
9
10
|
},
|
|
10
11
|
},
|
|
@@ -100,6 +101,9 @@ exports.versionedComponents = {
|
|
|
100
101
|
},
|
|
101
102
|
},
|
|
102
103
|
BarGauge: {
|
|
104
|
+
/**
|
|
105
|
+
* @deprecated use valueV2 from Grafana 8.3 instead
|
|
106
|
+
*/
|
|
103
107
|
value: 'Bar gauge value',
|
|
104
108
|
valueV2: 'data-testid Bar gauge value',
|
|
105
109
|
},
|
|
@@ -113,6 +117,7 @@ exports.versionedComponents = {
|
|
|
113
117
|
header: 'table header',
|
|
114
118
|
footer: 'table-footer',
|
|
115
119
|
body: {
|
|
120
|
+
// did not exist prior to 10.2.0
|
|
116
121
|
'10.2.0': 'data-testid table body',
|
|
117
122
|
},
|
|
118
123
|
},
|
|
@@ -139,6 +144,7 @@ exports.versionedComponents = {
|
|
|
139
144
|
select: 'Panel editor option pane select',
|
|
140
145
|
fieldLabel: (type) => `${type} field property editor`,
|
|
141
146
|
},
|
|
147
|
+
// not sure about the naming *DataPane*
|
|
142
148
|
DataPane: {
|
|
143
149
|
content: 'Panel editor data pane content',
|
|
144
150
|
},
|
|
@@ -149,6 +155,7 @@ exports.versionedComponents = {
|
|
|
149
155
|
},
|
|
150
156
|
toggleVizOptions: 'data-testid toggle-viz-options',
|
|
151
157
|
toggleTableView: 'toggle-table-view',
|
|
158
|
+
// [Geomap] Map controls
|
|
152
159
|
showZoomField: 'Map controls Show zoom control field property editor',
|
|
153
160
|
showAttributionField: 'Map controls Show attribution field property editor',
|
|
154
161
|
showScaleField: 'Map controls Show scale field property editor',
|
|
@@ -177,7 +184,13 @@ exports.versionedComponents = {
|
|
|
177
184
|
active: () => '[class*="-activeTabStyle"]',
|
|
178
185
|
},
|
|
179
186
|
RefreshPicker: {
|
|
187
|
+
/**
|
|
188
|
+
* @deprecated use runButtonV2 from Grafana 8.3 instead
|
|
189
|
+
*/
|
|
180
190
|
runButton: 'RefreshPicker run button',
|
|
191
|
+
/**
|
|
192
|
+
* @deprecated use intervalButtonV2 from Grafana 8.3 instead
|
|
193
|
+
*/
|
|
181
194
|
intervalButton: 'RefreshPicker interval button',
|
|
182
195
|
runButtonV2: 'data-testid RefreshPicker run button',
|
|
183
196
|
intervalButtonV2: 'data-testid RefreshPicker interval button',
|
|
@@ -203,6 +216,9 @@ exports.versionedComponents = {
|
|
|
203
216
|
content: 'Alert editor tab content',
|
|
204
217
|
},
|
|
205
218
|
Alert: {
|
|
219
|
+
/**
|
|
220
|
+
* @deprecated use alertV2 from Grafana 8.3 instead
|
|
221
|
+
*/
|
|
206
222
|
alert: (severity) => `Alert ${severity}`,
|
|
207
223
|
alertV2: (severity) => `data-testid Alert ${severity}`,
|
|
208
224
|
},
|
|
@@ -280,6 +296,9 @@ exports.versionedComponents = {
|
|
|
280
296
|
OptionsGroup: {
|
|
281
297
|
group: (title) => (title ? `Options group ${title}` : 'Options group'),
|
|
282
298
|
toggle: (title) => (title ? `Options group ${title} toggle` : 'Options group toggle'),
|
|
299
|
+
groupTitle: {
|
|
300
|
+
[constants_1.MIN_GRAFANA_VERSION]: 'Panel options',
|
|
301
|
+
},
|
|
283
302
|
},
|
|
284
303
|
PluginVisualization: {
|
|
285
304
|
item: (title) => `Plugin visualization item ${title}`,
|
|
@@ -297,6 +316,9 @@ exports.versionedComponents = {
|
|
|
297
316
|
content: 'Field overrides editor content',
|
|
298
317
|
},
|
|
299
318
|
FolderPicker: {
|
|
319
|
+
/**
|
|
320
|
+
* @deprecated use containerV2 from Grafana 8.3 instead
|
|
321
|
+
*/
|
|
300
322
|
container: 'Folder picker select container',
|
|
301
323
|
containerV2: 'data-testid Folder picker select container',
|
|
302
324
|
input: 'Select a folder',
|
|
@@ -307,16 +329,26 @@ exports.versionedComponents = {
|
|
|
307
329
|
DataSourcePicker: {
|
|
308
330
|
container: {
|
|
309
331
|
'10.0.0': 'data-testid Data source picker select container',
|
|
332
|
+
// did not exist prior to 8.3.0
|
|
310
333
|
'8.3.0': 'Data source picker select container',
|
|
311
334
|
},
|
|
335
|
+
/**
|
|
336
|
+
* @deprecated use inputV2 instead
|
|
337
|
+
*/
|
|
312
338
|
input: () => 'input[id="data-source-picker"]',
|
|
313
339
|
inputV2: 'data-testid Select a data source',
|
|
314
340
|
},
|
|
315
341
|
TimeZonePicker: {
|
|
342
|
+
/**
|
|
343
|
+
* @deprecated use TimeZonePicker.containerV2 from Grafana 8.3 instead
|
|
344
|
+
*/
|
|
316
345
|
container: 'Time zone picker select container',
|
|
317
346
|
containerV2: 'data-testid Time zone picker select container',
|
|
318
347
|
},
|
|
319
348
|
WeekStartPicker: {
|
|
349
|
+
/**
|
|
350
|
+
* @deprecated use WeekStartPicker.containerV2 from Grafana 8.3 instead
|
|
351
|
+
*/
|
|
320
352
|
container: 'Choose starting day of the week',
|
|
321
353
|
containerV2: 'data-testid Choose starting day of the week',
|
|
322
354
|
placeholder: 'Choose starting day of the week',
|
|
@@ -336,8 +368,14 @@ exports.versionedComponents = {
|
|
|
336
368
|
select: (name) => `Value picker select ${name}`,
|
|
337
369
|
},
|
|
338
370
|
Search: {
|
|
371
|
+
/**
|
|
372
|
+
* @deprecated use sectionV2 from Grafana 8.3 instead
|
|
373
|
+
*/
|
|
339
374
|
section: 'Search section',
|
|
340
375
|
sectionV2: 'data-testid Search section',
|
|
376
|
+
/**
|
|
377
|
+
* @deprecated use itemsV2 from Grafana 8.3 instead
|
|
378
|
+
*/
|
|
341
379
|
items: 'Search items',
|
|
342
380
|
itemsV2: 'data-testid Search items',
|
|
343
381
|
cards: 'data-testid Search cards',
|
|
@@ -357,6 +395,9 @@ exports.versionedComponents = {
|
|
|
357
395
|
icon: 'Loading indicator',
|
|
358
396
|
},
|
|
359
397
|
CallToActionCard: {
|
|
398
|
+
/**
|
|
399
|
+
* @deprecated use buttonV2 from Grafana 8.3 instead
|
|
400
|
+
*/
|
|
360
401
|
button: (name) => `Call to action button ${name}`,
|
|
361
402
|
buttonV2: (name) => `data-testid Call to action button ${name}`,
|
|
362
403
|
},
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selectors grouped/defined in Pages
|
|
3
|
+
*
|
|
4
|
+
* @alpha
|
|
5
|
+
*/
|
|
1
6
|
export declare const versionedPages: {
|
|
2
7
|
Login: {
|
|
3
8
|
url: string;
|
|
@@ -35,6 +40,7 @@ export declare const versionedPages: {
|
|
|
35
40
|
'8.5.0': string;
|
|
36
41
|
'10.1.0': string;
|
|
37
42
|
};
|
|
43
|
+
/** @deprecated Use dataSourcePluginsV2 */
|
|
38
44
|
dataSourcePlugins: {
|
|
39
45
|
'8.5.0': (pluginName: string) => string;
|
|
40
46
|
'9.5.0': (pluginName: string) => string;
|
|
@@ -75,6 +81,9 @@ export declare const versionedPages: {
|
|
|
75
81
|
Dashboard: {
|
|
76
82
|
url: (uid: string) => string;
|
|
77
83
|
DashNav: {
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated use navV2 from Grafana 8.3 instead
|
|
86
|
+
*/
|
|
78
87
|
nav: string;
|
|
79
88
|
navV2: string;
|
|
80
89
|
publicDashboardTag: string;
|
|
@@ -101,6 +110,9 @@ export declare const versionedPages: {
|
|
|
101
110
|
sectionItems: (item: string) => string;
|
|
102
111
|
saveDashBoard: string;
|
|
103
112
|
saveAsDashBoard: string;
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated use components.TimeZonePicker.containerV2 from Grafana 8.3 instead
|
|
115
|
+
*/
|
|
104
116
|
timezone: string;
|
|
105
117
|
title: string;
|
|
106
118
|
};
|
|
@@ -145,6 +157,9 @@ export declare const versionedPages: {
|
|
|
145
157
|
General: {
|
|
146
158
|
headerLink: string;
|
|
147
159
|
modeLabelNew: string;
|
|
160
|
+
/**
|
|
161
|
+
* @deprecated
|
|
162
|
+
*/
|
|
148
163
|
modeLabelEdit: string;
|
|
149
164
|
generalNameInput: string;
|
|
150
165
|
generalNameInputV2: string;
|
|
@@ -207,6 +222,9 @@ export declare const versionedPages: {
|
|
|
207
222
|
};
|
|
208
223
|
Dashboards: {
|
|
209
224
|
url: string;
|
|
225
|
+
/**
|
|
226
|
+
* @deprecated use components.Search.dashboardItem from Grafana 8.3 instead
|
|
227
|
+
*/
|
|
210
228
|
dashboards: (title: string) => string;
|
|
211
229
|
};
|
|
212
230
|
SaveDashboardAsModal: {
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.versionedPages = void 0;
|
|
4
4
|
const constants_1 = require("./constants");
|
|
5
|
+
/**
|
|
6
|
+
* Selectors grouped/defined in Pages
|
|
7
|
+
*
|
|
8
|
+
* @alpha
|
|
9
|
+
*/
|
|
5
10
|
exports.versionedPages = {
|
|
6
11
|
Login: {
|
|
7
12
|
url: '/login',
|
|
@@ -39,6 +44,7 @@ exports.versionedPages = {
|
|
|
39
44
|
'8.5.0': '/datasources/new',
|
|
40
45
|
'10.1.0': '/connections/datasources/new',
|
|
41
46
|
},
|
|
47
|
+
/** @deprecated Use dataSourcePluginsV2 */
|
|
42
48
|
dataSourcePlugins: {
|
|
43
49
|
'8.5.0': (pluginName) => `Data source plugin item ${pluginName}`,
|
|
44
50
|
'9.5.0': (pluginName) => `Add new data source ${pluginName}`,
|
|
@@ -79,6 +85,9 @@ exports.versionedPages = {
|
|
|
79
85
|
Dashboard: {
|
|
80
86
|
url: (uid) => `/d/${uid}`,
|
|
81
87
|
DashNav: {
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated use navV2 from Grafana 8.3 instead
|
|
90
|
+
*/
|
|
82
91
|
nav: 'Dashboard navigation',
|
|
83
92
|
navV2: 'data-testid Dashboard navigation',
|
|
84
93
|
publicDashboardTag: 'data-testid public dashboard tag',
|
|
@@ -105,6 +114,9 @@ exports.versionedPages = {
|
|
|
105
114
|
sectionItems: (item) => `Dashboard settings section item ${item}`,
|
|
106
115
|
saveDashBoard: 'Dashboard settings aside actions Save button',
|
|
107
116
|
saveAsDashBoard: 'Dashboard settings aside actions Save As button',
|
|
117
|
+
/**
|
|
118
|
+
* @deprecated use components.TimeZonePicker.containerV2 from Grafana 8.3 instead
|
|
119
|
+
*/
|
|
108
120
|
timezone: 'Time zone picker select container',
|
|
109
121
|
title: 'Tab General',
|
|
110
122
|
},
|
|
@@ -149,6 +161,9 @@ exports.versionedPages = {
|
|
|
149
161
|
General: {
|
|
150
162
|
headerLink: 'Variable editor Header link',
|
|
151
163
|
modeLabelNew: 'Variable editor Header mode New',
|
|
164
|
+
/**
|
|
165
|
+
* @deprecated
|
|
166
|
+
*/
|
|
152
167
|
modeLabelEdit: 'Variable editor Header mode Edit',
|
|
153
168
|
generalNameInput: 'Variable editor Form Name field',
|
|
154
169
|
generalNameInputV2: 'data-testid Variable editor Form Name field',
|
|
@@ -211,6 +226,9 @@ exports.versionedPages = {
|
|
|
211
226
|
},
|
|
212
227
|
Dashboards: {
|
|
213
228
|
url: '/dashboards',
|
|
229
|
+
/**
|
|
230
|
+
* @deprecated use components.Search.dashboardItem from Grafana 8.3 instead
|
|
231
|
+
*/
|
|
214
232
|
dashboards: (title) => `Dashboard search item ${title}`,
|
|
215
233
|
},
|
|
216
234
|
SaveDashboardAsModal: {
|
|
@@ -7,5 +7,9 @@ export declare class AnnotationEditPage extends GrafanaPage {
|
|
|
7
7
|
datasource: DataSourcePicker;
|
|
8
8
|
constructor(ctx: PluginTestCtx, args: DashboardEditViewArgs<string>);
|
|
9
9
|
goto(options?: NavigateOptions): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Executes the annotation query defined in the annotation page and returns the response promise
|
|
12
|
+
* @param options - Optional. RequestOptions to pass to waitForResponse
|
|
13
|
+
*/
|
|
10
14
|
runQuery(options?: RequestOptions): Promise<import("playwright-core").Response>;
|
|
11
15
|
}
|
|
@@ -20,8 +20,13 @@ class AnnotationEditPage extends GrafanaPage_1.GrafanaPage {
|
|
|
20
20
|
: AddDashboard.Settings.Annotations.Edit.url(this.args.id);
|
|
21
21
|
return super.navigate(url, options);
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Executes the annotation query defined in the annotation page and returns the response promise
|
|
25
|
+
* @param options - Optional. RequestOptions to pass to waitForResponse
|
|
26
|
+
*/
|
|
23
27
|
async runQuery(options) {
|
|
24
28
|
const responsePromise = this.ctx.page.waitForResponse((resp) => resp.url().includes(this.ctx.selectors.apis.DataSource.query), options);
|
|
29
|
+
//TODO: add new selector and use it in grafana/ui
|
|
25
30
|
await this.ctx.page.getByRole('button', { name: 'TEST' }).click();
|
|
26
31
|
return responsePromise;
|
|
27
32
|
}
|
|
@@ -45,6 +45,7 @@ class AnnotationPage extends GrafanaPage_1.GrafanaPage {
|
|
|
45
45
|
async clickAddNew() {
|
|
46
46
|
const { Dashboard } = this.ctx.selectors.pages;
|
|
47
47
|
if (!this.dashboard?.uid) {
|
|
48
|
+
//the dashboard doesn't have any annotations yet (except for the built-in one)
|
|
48
49
|
if (semver.gte(this.ctx.grafanaVersion, '8.3.0')) {
|
|
49
50
|
await this.getByTestIdOrAriaLabel(Dashboard.Settings.Annotations.List.addAnnotationCTAV2).click();
|
|
50
51
|
}
|
|
@@ -53,6 +54,8 @@ class AnnotationPage extends GrafanaPage_1.GrafanaPage {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
else {
|
|
57
|
+
//the dashboard already has annotations
|
|
58
|
+
//TODO: add new selector and use it in grafana/ui
|
|
56
59
|
await this.ctx.page.getByRole('button', { name: 'New query' }).click();
|
|
57
60
|
}
|
|
58
61
|
const editIndex = await this.ctx.page.evaluate(() => {
|
|
@@ -5,6 +5,16 @@ export declare class DataSourceConfigPage extends GrafanaPage {
|
|
|
5
5
|
constructor(ctx: PluginTestCtx, datasource: DataSource);
|
|
6
6
|
deleteDataSource(): Promise<void>;
|
|
7
7
|
goto(options?: NavigateOptions): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Mocks the response of the datasource health check call
|
|
10
|
+
* @param json the json response to return
|
|
11
|
+
* @param status the HTTP status code to return. Defaults to 200
|
|
12
|
+
*/
|
|
8
13
|
mockHealthCheckResponse<T = any>(json: T, status?: number): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Clicks the save and test button and waits for the response
|
|
16
|
+
*
|
|
17
|
+
* Optionally, you can skip waiting for the response by passing in { skipWaitForResponse: true } as the options parameter
|
|
18
|
+
*/
|
|
9
19
|
saveAndTest(options?: TriggerQueryOptions): Promise<void | import("playwright-core").Response>;
|
|
10
20
|
}
|
|
@@ -14,11 +14,21 @@ class DataSourceConfigPage extends GrafanaPage_1.GrafanaPage {
|
|
|
14
14
|
async goto(options) {
|
|
15
15
|
return super.navigate(this.ctx.selectors.pages.EditDataSource.url(this.datasource.uid), options);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Mocks the response of the datasource health check call
|
|
19
|
+
* @param json the json response to return
|
|
20
|
+
* @param status the HTTP status code to return. Defaults to 200
|
|
21
|
+
*/
|
|
17
22
|
async mockHealthCheckResponse(json, status = 200) {
|
|
18
23
|
await this.ctx.page.route(`${this.ctx.selectors.apis.DataSource.health(this.datasource.uid ?? '', this.datasource.id.toString() ?? '')}`, async (route) => {
|
|
19
24
|
await route.fulfill({ json, status });
|
|
20
25
|
});
|
|
21
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Clicks the save and test button and waits for the response
|
|
29
|
+
*
|
|
30
|
+
* Optionally, you can skip waiting for the response by passing in { skipWaitForResponse: true } as the options parameter
|
|
31
|
+
*/
|
|
22
32
|
async saveAndTest(options) {
|
|
23
33
|
if (options?.skipWaitForResponse) {
|
|
24
34
|
return this.getByTestIdOrAriaLabel(this.ctx.selectors.pages.DataSource.saveAndTest).click();
|
|
@@ -10,6 +10,8 @@ class DataSourcePicker extends GrafanaPage_1.GrafanaPage {
|
|
|
10
10
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.DataSourcePicker.container)
|
|
11
11
|
.locator('input')
|
|
12
12
|
.fill(name);
|
|
13
|
+
// this is a hack to get the selection to work in 10.ish versions of Grafana.
|
|
14
|
+
// TODO: investigate if the select component can somehow be refactored so that its easier to test with playwright
|
|
13
15
|
await this.ctx.page.keyboard.press('ArrowDown');
|
|
14
16
|
await this.ctx.page.keyboard.press('ArrowUp');
|
|
15
17
|
await this.ctx.page.keyboard.press('Enter');
|
|
@@ -32,6 +32,7 @@ class ExplorePage extends GrafanaPage_1.GrafanaPage {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
catch (_) {
|
|
35
|
+
// handle the case when the run button is hidden behind the "Show more items" button
|
|
35
36
|
await this.getByTestIdOrAriaLabel(components.PageToolbar.item(components.PageToolbar.shotMoreItems)).click();
|
|
36
37
|
await this.getByTestIdOrAriaLabel(components.RefreshPicker.runButtonV2).last().click();
|
|
37
38
|
}
|
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
import { Locator, Request, Response } from '@playwright/test';
|
|
2
2
|
import { NavigateOptions, PluginTestCtx } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for all Grafana pages.
|
|
5
|
+
*
|
|
6
|
+
* Exposes methods for locating Grafana specific elements on the page
|
|
7
|
+
*/
|
|
3
8
|
export declare abstract class GrafanaPage {
|
|
4
9
|
readonly ctx: PluginTestCtx;
|
|
5
10
|
constructor(ctx: PluginTestCtx);
|
|
6
11
|
protected navigate(url: string, options?: NavigateOptions): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Get a locator for a Grafana element by data-testid or aria-label
|
|
14
|
+
* @param selector the data-testid or aria-label of the element
|
|
15
|
+
* @param root optional root locator to search within. If no locator is provided, the page will be used
|
|
16
|
+
*/
|
|
7
17
|
getByTestIdOrAriaLabel(selector: string, root?: Locator): Locator;
|
|
18
|
+
/**
|
|
19
|
+
* Mocks the response of the datasource query call
|
|
20
|
+
* @param json the json response to return
|
|
21
|
+
* @param status the HTTP status code to return. Defaults to 200
|
|
22
|
+
*/
|
|
8
23
|
mockQueryDataResponse<T = any>(json: T, status?: number): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Mocks the response of the datasource resource request
|
|
26
|
+
* @param path the path of the resource to mock
|
|
27
|
+
* @param json the json response to return
|
|
28
|
+
* @param status the HTTP status code to return. Defaults to 200
|
|
29
|
+
*/
|
|
9
30
|
mockResourceResponse<T = any>(path: string, json: T, status?: number): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Waits for a data source query data request to be made.
|
|
33
|
+
*
|
|
34
|
+
* @param cb optional callback to filter the request. Use this to filter by request body or other request properties
|
|
35
|
+
*/
|
|
10
36
|
waitForQueryDataRequest(cb?: (request: Request) => boolean | Promise<boolean>): Promise<Request>;
|
|
37
|
+
/**
|
|
38
|
+
* Waits for a data source query data response
|
|
39
|
+
*
|
|
40
|
+
* @param cb optional callback to filter the response. Use this to filter by response body or other response properties
|
|
41
|
+
*/
|
|
11
42
|
waitForQueryDataResponse(cb?: (request: Response) => boolean | Promise<boolean>): Promise<Response>;
|
|
12
43
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GrafanaPage = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base class for all Grafana pages.
|
|
6
|
+
*
|
|
7
|
+
* Exposes methods for locating Grafana specific elements on the page
|
|
8
|
+
*/
|
|
4
9
|
class GrafanaPage {
|
|
5
10
|
ctx;
|
|
6
11
|
constructor(ctx) {
|
|
@@ -15,25 +20,47 @@ class GrafanaPage {
|
|
|
15
20
|
...options,
|
|
16
21
|
});
|
|
17
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Get a locator for a Grafana element by data-testid or aria-label
|
|
25
|
+
* @param selector the data-testid or aria-label of the element
|
|
26
|
+
* @param root optional root locator to search within. If no locator is provided, the page will be used
|
|
27
|
+
*/
|
|
18
28
|
getByTestIdOrAriaLabel(selector, root) {
|
|
19
29
|
if (selector.startsWith('data-testid')) {
|
|
20
30
|
return (root || this.ctx.page).getByTestId(selector);
|
|
21
31
|
}
|
|
22
32
|
return (root || this.ctx.page).locator(`[aria-label="${selector}"]`);
|
|
23
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Mocks the response of the datasource query call
|
|
36
|
+
* @param json the json response to return
|
|
37
|
+
* @param status the HTTP status code to return. Defaults to 200
|
|
38
|
+
*/
|
|
24
39
|
async mockQueryDataResponse(json, status = 200) {
|
|
25
40
|
await this.ctx.page.route(this.ctx.selectors.apis.DataSource.queryPattern, async (route) => {
|
|
26
41
|
await route.fulfill({ json, status });
|
|
27
42
|
});
|
|
28
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Mocks the response of the datasource resource request
|
|
46
|
+
* @param path the path of the resource to mock
|
|
47
|
+
* @param json the json response to return
|
|
48
|
+
* @param status the HTTP status code to return. Defaults to 200
|
|
49
|
+
*/
|
|
29
50
|
async mockResourceResponse(path, json, status = 200) {
|
|
30
51
|
await this.ctx.page.route(`${this.ctx.selectors.apis.DataSource.resourceUIDPattern}/${path}`, async (route) => {
|
|
31
52
|
await route.fulfill({ json, status });
|
|
32
53
|
});
|
|
54
|
+
// some data sources use the backendSrv directly, and then the path may be different
|
|
33
55
|
await this.ctx.page.route(`${this.ctx.selectors.apis.DataSource.resourcePattern}/${path}`, async (route) => {
|
|
34
56
|
await route.fulfill({ json, status });
|
|
35
57
|
});
|
|
36
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Waits for a data source query data request to be made.
|
|
61
|
+
*
|
|
62
|
+
* @param cb optional callback to filter the request. Use this to filter by request body or other request properties
|
|
63
|
+
*/
|
|
37
64
|
async waitForQueryDataRequest(cb) {
|
|
38
65
|
return this.ctx.page.waitForRequest((request) => {
|
|
39
66
|
if (request.url().includes(this.ctx.selectors.apis.DataSource.query) && request.method() === 'POST') {
|
|
@@ -42,6 +69,11 @@ class GrafanaPage {
|
|
|
42
69
|
return false;
|
|
43
70
|
});
|
|
44
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Waits for a data source query data response
|
|
74
|
+
*
|
|
75
|
+
* @param cb optional callback to filter the response. Use this to filter by response body or other response properties
|
|
76
|
+
*/
|
|
45
77
|
async waitForQueryDataResponse(cb) {
|
|
46
78
|
return this.ctx.page.waitForResponse((response) => {
|
|
47
79
|
if (response.url().includes(this.ctx.selectors.apis.DataSource.query)) {
|
|
@@ -10,7 +10,9 @@ export declare class PanelEditPage extends GrafanaPage implements PanelError {
|
|
|
10
10
|
timeRange: TimeRange;
|
|
11
11
|
constructor(ctx: PluginTestCtx, args: DashboardEditViewArgs<string>);
|
|
12
12
|
goto(options?: NavigateOptions): Promise<void>;
|
|
13
|
+
setPanelTitle(title: string): Promise<void>;
|
|
13
14
|
setVisualization(visualization: Visualization): Promise<void>;
|
|
15
|
+
getVisualizationName(): Locator;
|
|
14
16
|
apply(): Promise<void>;
|
|
15
17
|
getQueryEditorRow(refId: string): Promise<Locator>;
|
|
16
18
|
getPanelError(): Locator;
|
|
@@ -51,11 +51,28 @@ class PanelEditPage extends GrafanaPage_1.GrafanaPage {
|
|
|
51
51
|
options.queryParams.append('editPanel', this.args.id);
|
|
52
52
|
await super.navigate(url, options);
|
|
53
53
|
}
|
|
54
|
+
async setPanelTitle(title) {
|
|
55
|
+
const { OptionsGroup } = this.ctx.selectors.components;
|
|
56
|
+
//TODO: add new selector and use it in grafana/ui
|
|
57
|
+
const vizInput = await this.getByTestIdOrAriaLabel(OptionsGroup.group(OptionsGroup.groupTitle))
|
|
58
|
+
.locator('input')
|
|
59
|
+
.first();
|
|
60
|
+
const isVisible = await vizInput.isVisible();
|
|
61
|
+
if (!isVisible) {
|
|
62
|
+
// expand panel options if not visible
|
|
63
|
+
//TODO: add new selector and use it in grafana/ui
|
|
64
|
+
await this.getByTestIdOrAriaLabel(OptionsGroup.group(OptionsGroup.groupTitle)).locator('button').click();
|
|
65
|
+
}
|
|
66
|
+
await vizInput.fill(title);
|
|
67
|
+
}
|
|
54
68
|
async setVisualization(visualization) {
|
|
55
69
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.toggleVizPicker).click();
|
|
56
70
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PluginVisualization.item(visualization)).click();
|
|
57
71
|
await (0, test_1.expect)(this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.toggleVizPicker), `Could not set visualization to ${visualization}. Ensure the panel is installed.`).toHaveText(visualization);
|
|
58
72
|
}
|
|
73
|
+
getVisualizationName() {
|
|
74
|
+
return this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.toggleVizPicker);
|
|
75
|
+
}
|
|
59
76
|
async apply() {
|
|
60
77
|
await this.ctx.page.getByTestId(this.ctx.selectors.components.PanelEditor.applyButton).click();
|
|
61
78
|
}
|
|
@@ -67,6 +84,7 @@ class PanelEditPage extends GrafanaPage_1.GrafanaPage {
|
|
|
67
84
|
return locator;
|
|
68
85
|
}
|
|
69
86
|
getPanelError() {
|
|
87
|
+
// the selector (not the selector value) used to identify a panel error changed in 9.4.3
|
|
70
88
|
if (semver.lte(this.ctx.grafanaVersion, '9.4.3')) {
|
|
71
89
|
return this.getByTestIdOrAriaLabel(this.ctx.selectors.components.Panels.Panel.headerCornerInfo(ERROR_STATUS));
|
|
72
90
|
}
|
|
@@ -74,6 +92,7 @@ class PanelEditPage extends GrafanaPage_1.GrafanaPage {
|
|
|
74
92
|
}
|
|
75
93
|
async refreshPanel(options) {
|
|
76
94
|
const responsePromise = this.ctx.page.waitForResponse((resp) => resp.url().includes(this.ctx.selectors.apis.DataSource.query), options);
|
|
95
|
+
// in older versions of grafana, the refresh button is rendered twice. this is a workaround to click the correct one
|
|
77
96
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.General.content)
|
|
78
97
|
.locator(`selector=${this.ctx.selectors.components.RefreshPicker.runButtonV2}`)
|
|
79
98
|
.click();
|
package/dist/models/TimeRange.js
CHANGED
|
@@ -11,9 +11,11 @@ class TimeRange extends GrafanaPage_1.GrafanaPage {
|
|
|
11
11
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimePicker.openButton).click();
|
|
12
12
|
}
|
|
13
13
|
catch (e) {
|
|
14
|
+
// seems like in older versions of Grafana the time picker markup is rendered twice
|
|
14
15
|
await this.ctx.page.locator('[aria-controls="TimePickerContent"]').last().click();
|
|
15
16
|
}
|
|
16
17
|
if (zone) {
|
|
18
|
+
//todo: add an e2e selector for the time zone picker and use it in grafana ui
|
|
17
19
|
await this.ctx.page.getByRole('button', { name: 'Change time settings' }).click();
|
|
18
20
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimeZonePicker.containerV2).fill(zone);
|
|
19
21
|
}
|
|
@@ -9,5 +9,12 @@ export declare class VariableEditPage extends GrafanaPage {
|
|
|
9
9
|
constructor(ctx: PluginTestCtx, args: DashboardEditViewArgs<string>);
|
|
10
10
|
goto(options?: NavigateOptions): Promise<void>;
|
|
11
11
|
setVariableType(type: VariableType): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Triggers the variable query to run. Note that unlike {@link PanelEditPage.refreshPanel}, this method doesn't
|
|
14
|
+
* return a request promise. This is because there's no canonical way of querying variables - data sources may
|
|
15
|
+
* call any endpoint or resolve variables in the frontend. If you need to wait for a specific request, you can
|
|
16
|
+
* do that in your test.
|
|
17
|
+
* @example await this.ctx.page.waitForResponse((resp) => resp.url().includes('<url>')
|
|
18
|
+
*/
|
|
12
19
|
runQuery(): Promise<void>;
|
|
13
20
|
}
|
|
@@ -29,11 +29,20 @@ class VariableEditPage extends GrafanaPage_1.GrafanaPage {
|
|
|
29
29
|
await this.ctx.page.keyboard.press('Enter');
|
|
30
30
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2).scrollIntoViewIfNeeded();
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Triggers the variable query to run. Note that unlike {@link PanelEditPage.refreshPanel}, this method doesn't
|
|
34
|
+
* return a request promise. This is because there's no canonical way of querying variables - data sources may
|
|
35
|
+
* call any endpoint or resolve variables in the frontend. If you need to wait for a specific request, you can
|
|
36
|
+
* do that in your test.
|
|
37
|
+
* @example await this.ctx.page.waitForResponse((resp) => resp.url().includes('<url>')
|
|
38
|
+
*/
|
|
32
39
|
async runQuery() {
|
|
40
|
+
// in 9.2.0, the submit button got a new purpose. it no longer submits the form, but instead runs the query
|
|
33
41
|
if (gte(this.ctx.grafanaVersion, '9.2.0')) {
|
|
34
42
|
await this.getByTestIdOrAriaLabel(this.ctx.selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton).click();
|
|
35
43
|
}
|
|
36
44
|
else {
|
|
45
|
+
// in 9.1.3, the submit button submits the form
|
|
37
46
|
await this.ctx.page.keyboard.press('Tab');
|
|
38
47
|
}
|
|
39
48
|
}
|
package/dist/selectorEngine.d.ts
CHANGED
package/dist/selectorEngine.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.grafanaE2ESelectorEngine = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Returns a selector engine that resolves selectors by data-testid or aria-label
|
|
6
|
+
*/
|
|
4
7
|
const grafanaE2ESelectorEngine = () => ({
|
|
8
|
+
// Returns the first element matching given selector in the root's subtree.
|
|
5
9
|
query(root, selector) {
|
|
6
10
|
if (selector.startsWith('data-testid')) {
|
|
7
11
|
return root.querySelector(`[data-testid="${selector}"]`);
|
|
8
12
|
}
|
|
9
13
|
return root.querySelector(`[aria-label="${selector}"]`);
|
|
10
14
|
},
|
|
15
|
+
// Returns all elements matching given selector in the root's subtree.
|
|
11
16
|
queryAll(root, selector) {
|
|
12
17
|
if (selector.startsWith('data-testid')) {
|
|
13
18
|
return root.querySelectorAll(`[data-testid="${selector}"]`);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { Locator, PlaywrightTestArgs } from '@playwright/test';
|
|
2
2
|
import { E2ESelectors } from './e2e-selectors/types';
|
|
3
|
+
/**
|
|
4
|
+
* The context object passed to page object models
|
|
5
|
+
*/
|
|
3
6
|
export type PluginTestCtx = {
|
|
4
7
|
grafanaVersion: string;
|
|
5
8
|
selectors: E2ESelectors;
|
|
6
9
|
} & Pick<PlaywrightTestArgs, 'page' | 'request'>;
|
|
10
|
+
/**
|
|
11
|
+
* The data source object
|
|
12
|
+
*/
|
|
7
13
|
export interface DataSource<T = any> {
|
|
8
14
|
id?: number;
|
|
9
15
|
editable?: boolean;
|
|
@@ -18,56 +24,167 @@ export interface DataSource<T = any> {
|
|
|
18
24
|
jsonData?: T;
|
|
19
25
|
secureJsonData?: T;
|
|
20
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The dashboard object
|
|
29
|
+
*/
|
|
21
30
|
export interface Dashboard {
|
|
22
31
|
uid: string;
|
|
23
32
|
title?: string;
|
|
24
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The YAML provision file parsed to a javascript object
|
|
36
|
+
*/
|
|
25
37
|
export type ProvisionFile<T = DataSource> = {
|
|
26
38
|
datasources: Array<DataSource<T>>;
|
|
27
39
|
};
|
|
28
40
|
export type CreateDataSourceArgs = {
|
|
41
|
+
/**
|
|
42
|
+
* The data source to create
|
|
43
|
+
*/
|
|
29
44
|
datasource: DataSource;
|
|
30
45
|
};
|
|
31
46
|
export type CreateDataSourcePageArgs = {
|
|
47
|
+
/**
|
|
48
|
+
* The data source type to create
|
|
49
|
+
*/
|
|
32
50
|
type: string;
|
|
51
|
+
/**
|
|
52
|
+
* The data source name to create
|
|
53
|
+
*/
|
|
33
54
|
name?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Set this to false to delete the data source via Grafana API after the test. Defaults to true.
|
|
57
|
+
*/
|
|
34
58
|
deleteDataSourceAfterTest?: boolean;
|
|
35
59
|
};
|
|
36
60
|
export type RequestOptions = {
|
|
61
|
+
/**
|
|
62
|
+
* Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can
|
|
63
|
+
* be changed by using the
|
|
64
|
+
* [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
|
|
65
|
+
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
|
|
66
|
+
*/
|
|
37
67
|
timeout?: number;
|
|
38
68
|
};
|
|
39
69
|
export interface TimeRangeArgs {
|
|
70
|
+
/**
|
|
71
|
+
* The from time
|
|
72
|
+
* @example 'now-6h'
|
|
73
|
+
* @example '2020-01-01 00:00:00'
|
|
74
|
+
*/
|
|
40
75
|
from: string;
|
|
76
|
+
/**
|
|
77
|
+
* The to time
|
|
78
|
+
* @example 'now'
|
|
79
|
+
* @example '2020-01-01 00:00:00'
|
|
80
|
+
*/
|
|
41
81
|
to: string;
|
|
82
|
+
/**
|
|
83
|
+
* The time zone
|
|
84
|
+
* @example 'utc'
|
|
85
|
+
* @example 'browser'
|
|
86
|
+
*/
|
|
42
87
|
zone?: string;
|
|
43
88
|
}
|
|
44
89
|
export type DashboardPageArgs = {
|
|
90
|
+
/**
|
|
91
|
+
* The uid of the dashboard to go to
|
|
92
|
+
*/
|
|
45
93
|
uid?: string;
|
|
94
|
+
/**
|
|
95
|
+
* The time range to set
|
|
96
|
+
*/
|
|
46
97
|
timeRange?: TimeRangeArgs;
|
|
98
|
+
/**
|
|
99
|
+
* Query parameters to add to the url
|
|
100
|
+
*/
|
|
47
101
|
queryParams?: URLSearchParams;
|
|
48
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* DashboardEditViewArgs is used to pass arguments to the page object models that represent a dashboard edit view,
|
|
105
|
+
* such as {@link PanelEditPage}, {@link VariableEditPage} and {@link AnnotationEditPage}.
|
|
106
|
+
*
|
|
107
|
+
* If dashboard is not specified, it's assumed that it's a new dashboard. Otherwise, the dashboard uid is used to
|
|
108
|
+
* navigate to an already existing dashboard.
|
|
109
|
+
*/
|
|
49
110
|
export type DashboardEditViewArgs<T> = {
|
|
50
111
|
dashboard?: DashboardPageArgs;
|
|
51
112
|
id: T;
|
|
52
113
|
};
|
|
53
114
|
export type ReadProvisionArgs = {
|
|
115
|
+
/**
|
|
116
|
+
* The path, relative to the provisioning folder, to the dashboard json file
|
|
117
|
+
*/
|
|
54
118
|
filePath: string;
|
|
55
119
|
};
|
|
56
120
|
export type NavigateOptions = {
|
|
121
|
+
/**
|
|
122
|
+
* Referer header value.
|
|
123
|
+
*/
|
|
57
124
|
referer?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Maximum operation time in milliseconds. Defaults to `0` - no timeout.
|
|
127
|
+
*/
|
|
58
128
|
timeout?: number;
|
|
129
|
+
/**
|
|
130
|
+
* When to consider operation succeeded, defaults to `load`. Events can be either:
|
|
131
|
+
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
|
|
132
|
+
* - `'load'` - consider operation to be finished when the `load` event is fired.
|
|
133
|
+
* - `'networkidle'` - **DISCOURAGED** consider operation to be finished when there are no network connections for
|
|
134
|
+
* at least `500` ms. Don't use this method for testing, rely on web assertions to assess readiness instead.
|
|
135
|
+
* - `'commit'` - consider operation to be finished when network response is received and the document started
|
|
136
|
+
* loading.
|
|
137
|
+
*/
|
|
59
138
|
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
|
|
139
|
+
/**
|
|
140
|
+
* Query parameters to add to the url. Optional
|
|
141
|
+
*/
|
|
60
142
|
queryParams?: URLSearchParams;
|
|
61
143
|
};
|
|
62
144
|
export type TriggerQueryOptions = {
|
|
145
|
+
/**
|
|
146
|
+
* Set this to true to skip waiting for the response. Defaults to false.
|
|
147
|
+
*/
|
|
63
148
|
skipWaitForResponse: boolean;
|
|
64
149
|
};
|
|
150
|
+
/**
|
|
151
|
+
* Panel visualization types
|
|
152
|
+
*/
|
|
65
153
|
export type Visualization = 'Alert list' | 'Bar gauge' | 'Clock' | 'Dashboard list' | 'Gauge' | 'Graph' | 'Heatmap' | 'Logs' | 'News' | 'Pie Chart' | 'Plugin list' | 'Polystat' | 'Stat' | 'Table' | 'Text' | 'Time series' | 'Worldmap Panel';
|
|
66
154
|
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
|
|
67
155
|
export interface AlertPageOptions {
|
|
156
|
+
/**
|
|
157
|
+
* Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can
|
|
158
|
+
* be changed by using the
|
|
159
|
+
* [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
|
|
160
|
+
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
|
|
161
|
+
*/
|
|
68
162
|
timeout?: number;
|
|
163
|
+
/**
|
|
164
|
+
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
|
|
165
|
+
* one. For example, `article` that has `text=Playwright` matches `<article><div>Playwright</div></article>`.
|
|
166
|
+
*
|
|
167
|
+
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
|
|
168
|
+
* FrameLocator}s.
|
|
169
|
+
*/
|
|
69
170
|
has?: Locator;
|
|
171
|
+
/**
|
|
172
|
+
* Matches elements that do not contain an element that matches an inner locator. Inner locator is queried against the
|
|
173
|
+
* outer one. For example, `article` that does not have `div` matches `<article><span>Playwright</span></article>`.
|
|
174
|
+
*
|
|
175
|
+
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
|
|
176
|
+
* FrameLocator}s.
|
|
177
|
+
*/
|
|
70
178
|
hasNot?: Locator;
|
|
179
|
+
/**
|
|
180
|
+
* Matches elements that do not contain specified text somewhere inside, possibly in a child or a descendant element.
|
|
181
|
+
* When passed a [string], matching is case-insensitive and searches for a substring.
|
|
182
|
+
*/
|
|
71
183
|
hasNotText?: string | RegExp;
|
|
184
|
+
/**
|
|
185
|
+
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When
|
|
186
|
+
* passed a [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
|
|
187
|
+
* `<article><div>Playwright</div></article>`.
|
|
188
|
+
*/
|
|
72
189
|
hasText?: string | RegExp;
|
|
73
190
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/plugin-e2e",
|
|
3
|
-
"version": "0.1.0",
|
|
3
|
+
"version": "0.2.1-canary.616.211cb1f.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"semver": "^7.5.4",
|
|
45
45
|
"uuid": "^9.0.1"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "211cb1f6b8e7f8a6a7cfa9caed6ae2e8045b43b0"
|
|
48
48
|
}
|