From b09b49fb37be6276ae9a6344f23b7722d7755525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 27 Apr 2020 09:09:05 +0200 Subject: [PATCH] e2e: creates a separate package for selectors (#23858) * Initial commit * Chore: fixes after merge * Chore: removes todos * Chore: uncomment test * Chore: adds missing externals to rollup config * Refactor: selectors is master for everything * Docs: updates Docs * Chore: adds e2e-selectors to publish --- contribute/style-guides/e2e.md | 31 ++--- e2e/suite1/specs/1-smoketests.spec.ts | 4 +- e2e/suite1/specs/queryVariableCrud.spec.ts | 8 +- packages/grafana-e2e-selectors/CHANGELOG.md | 2 + packages/grafana-e2e-selectors/README.md | 3 + .../grafana-e2e-selectors/api-extractor.json | 3 + packages/grafana-e2e-selectors/index.js | 7 + packages/grafana-e2e-selectors/package.json | 50 +++++++ .../grafana-e2e-selectors/rollup.config.ts | 25 ++++ packages/grafana-e2e-selectors/src/index.ts | 7 + .../src/selectors/components.ts | 108 +++++++++++++++ .../src/selectors/index.ts | 8 ++ .../src/selectors/pages.ts | 121 ++++++++++++++++ .../grafana-e2e-selectors/src/types/index.ts | 1 + .../src/types/selectors.ts | 15 ++ .../grafana-e2e-selectors/tsconfig.build.json | 4 + packages/grafana-e2e-selectors/tsconfig.json | 11 ++ packages/grafana-e2e/package.json | 6 +- packages/grafana-e2e/rollup.config.ts | 1 + packages/grafana-e2e/src/components/index.ts | 129 ------------------ packages/grafana-e2e/src/flows/addPanel.ts | 4 +- .../src/flows/openPanelMenuItem.ts | 2 +- packages/grafana-e2e/src/index.ts | 22 ++- packages/grafana-e2e/src/noTypeCheck.ts | 28 ---- .../grafana-e2e/src/pages/addDashboard.ts | 8 -- .../grafana-e2e/src/pages/addDataSource.ts | 8 -- .../grafana-e2e/src/pages/confirmModal.ts | 8 -- packages/grafana-e2e/src/pages/dashboard.ts | 9 -- .../src/pages/dashboardSettings.ts | 11 -- packages/grafana-e2e/src/pages/dashboards.ts | 8 -- packages/grafana-e2e/src/pages/datasource.ts | 12 -- packages/grafana-e2e/src/pages/datasources.ts | 8 -- packages/grafana-e2e/src/pages/explore.ts | 9 -- packages/grafana-e2e/src/pages/graph/index.ts | 13 -- .../src/pages/graph/visualizationTab.ts | 11 -- packages/grafana-e2e/src/pages/index.ts | 47 ------- packages/grafana-e2e/src/pages/login.ts | 11 -- packages/grafana-e2e/src/pages/panel.ts | 9 -- .../src/pages/saveDashboardAsModal.ts | 9 -- .../src/pages/saveDashboardModal.ts | 10 -- .../grafana-e2e/src/pages/sharePanelModal.ts | 8 -- .../grafana-e2e/src/pages/testdata/index.ts | 5 - .../src/pages/testdata/queryTab.ts | 13 -- packages/grafana-e2e/src/pages/variables.ts | 69 ---------- packages/grafana-e2e/src/support/scenario.ts | 18 +-- packages/grafana-e2e/src/support/types.ts | 117 +++++++++------- packages/grafana-toolkit/src/cli/index.ts | 4 +- packages/grafana-ui/.eslintrc | 2 +- packages/grafana-ui/package.json | 1 + packages/grafana-ui/rollup.config.ts | 2 +- .../src/components/Drawer/Drawer.tsx | 13 +- .../grafana-ui/src/components/Tabs/Tab.tsx | 5 +- .../core/components/BackButton/BackButton.tsx | 4 +- .../core/components/Login/ChangePassword.tsx | 4 +- .../app/core/components/Login/LoginForm.tsx | 8 +- .../core/components/search/search_results.ts | 0 .../core/directives/value_select_dropdown.ts | 6 +- public/app/core/services/util_srv.ts | 4 +- public/app/features/alerting/AlertTab.tsx | 6 +- .../AddPanelWidget/AddPanelWidget.tsx | 4 +- .../components/DashNav/DashNavButton.tsx | 6 +- .../DashboardSettings/SettingsCtrl.ts | 6 +- .../components/Inspector/InspectDataTab.tsx | 7 +- .../components/Inspector/InspectJSONTab.tsx | 5 +- .../components/Inspector/PanelInspector.tsx | 4 +- .../components/Inspector/QueryInspector.tsx | 9 +- .../PanelEditor/FieldConfigEditor.tsx | 6 +- .../components/PanelEditor/OptionsGroup.tsx | 4 +- .../PanelEditor/OptionsPaneContent.tsx | 6 +- .../components/PanelEditor/PanelEditor.tsx | 6 +- .../SaveDashboard/forms/SaveDashboardForm.tsx | 11 +- .../components/ShareModal/ShareLink.tsx | 4 +- .../components/SubMenu/SubMenuCtrl.ts | 6 +- .../components/SubMenu/SubMenuItems.tsx | 4 +- .../TransformationsEditor.tsx | 4 +- .../dashgrid/PanelHeader/PanelHeader.tsx | 8 +- .../PanelHeader/PanelHeaderMenuItem.tsx | 4 +- .../dashboard/panel_editor/EditorTabBody.tsx | 7 +- .../dashboard/panel_editor/QueriesTab.tsx | 8 +- .../panel_editor/VizTypePickerPlugin.tsx | 4 +- .../datasources/DataSourcesListItem.tsx | 4 +- .../datasources/NewDataSourcePage.tsx | 8 +- .../datasources/settings/BasicSettings.tsx | 5 +- .../datasources/settings/ButtonRow.tsx | 6 +- .../settings/DataSourceSettingsPage.tsx | 8 +- public/app/features/explore/Explore.tsx | 48 +++---- public/app/features/explore/RunButton.tsx | 4 +- .../features/search/components/SearchItem.tsx | 4 +- .../templating/DefaultVariableQueryEditor.tsx | 4 +- public/app/features/templating/editor_ctrl.ts | 10 +- .../constant/ConstantVariableEditor.tsx | 6 +- .../editor/SelectionOptionsEditor.tsx | 15 +- .../editor/VariableEditorContainer.tsx | 13 +- .../variables/editor/VariableEditorEditor.tsx | 15 +- .../variables/editor/VariableEditorList.tsx | 19 +-- .../editor/VariableValuesPreview.tsx | 4 +- .../variables/pickers/PickerRenderer.tsx | 4 +- .../variables/pickers/shared/VariableLink.tsx | 5 +- .../pickers/shared/VariableOptions.tsx | 9 +- .../variables/query/QueryVariableEditor.tsx | 24 ++-- .../plugins/datasource/testdata/query_ctrl.ts | 6 +- .../panel/graph/Legend/LegendSeriesItem.tsx | 6 +- public/app/plugins/panel/graph/axes_editor.ts | 6 +- scripts/circle-release-next-packages.sh | 2 +- yarn.lock | 13 +- 105 files changed, 691 insertions(+), 760 deletions(-) create mode 100644 packages/grafana-e2e-selectors/CHANGELOG.md create mode 100644 packages/grafana-e2e-selectors/README.md create mode 100644 packages/grafana-e2e-selectors/api-extractor.json create mode 100644 packages/grafana-e2e-selectors/index.js create mode 100644 packages/grafana-e2e-selectors/package.json create mode 100644 packages/grafana-e2e-selectors/rollup.config.ts create mode 100644 packages/grafana-e2e-selectors/src/index.ts create mode 100644 packages/grafana-e2e-selectors/src/selectors/components.ts create mode 100644 packages/grafana-e2e-selectors/src/selectors/index.ts create mode 100644 packages/grafana-e2e-selectors/src/selectors/pages.ts create mode 100644 packages/grafana-e2e-selectors/src/types/index.ts create mode 100644 packages/grafana-e2e-selectors/src/types/selectors.ts create mode 100644 packages/grafana-e2e-selectors/tsconfig.build.json create mode 100644 packages/grafana-e2e-selectors/tsconfig.json delete mode 100644 packages/grafana-e2e/src/noTypeCheck.ts delete mode 100644 packages/grafana-e2e/src/pages/addDashboard.ts delete mode 100644 packages/grafana-e2e/src/pages/addDataSource.ts delete mode 100644 packages/grafana-e2e/src/pages/confirmModal.ts delete mode 100644 packages/grafana-e2e/src/pages/dashboard.ts delete mode 100644 packages/grafana-e2e/src/pages/dashboardSettings.ts delete mode 100644 packages/grafana-e2e/src/pages/dashboards.ts delete mode 100644 packages/grafana-e2e/src/pages/datasource.ts delete mode 100644 packages/grafana-e2e/src/pages/datasources.ts delete mode 100644 packages/grafana-e2e/src/pages/explore.ts delete mode 100644 packages/grafana-e2e/src/pages/graph/index.ts delete mode 100644 packages/grafana-e2e/src/pages/graph/visualizationTab.ts delete mode 100644 packages/grafana-e2e/src/pages/index.ts delete mode 100644 packages/grafana-e2e/src/pages/login.ts delete mode 100644 packages/grafana-e2e/src/pages/panel.ts delete mode 100644 packages/grafana-e2e/src/pages/saveDashboardAsModal.ts delete mode 100644 packages/grafana-e2e/src/pages/saveDashboardModal.ts delete mode 100644 packages/grafana-e2e/src/pages/sharePanelModal.ts delete mode 100644 packages/grafana-e2e/src/pages/testdata/index.ts delete mode 100644 packages/grafana-e2e/src/pages/testdata/queryTab.ts delete mode 100644 packages/grafana-e2e/src/pages/variables.ts create mode 100644 public/app/core/components/search/search_results.ts diff --git a/contribute/style-guides/e2e.md b/contribute/style-guides/e2e.md index 005097c477f..e0b5b026067 100644 --- a/contribute/style-guides/e2e.md +++ b/contribute/style-guides/e2e.md @@ -26,7 +26,8 @@ All the integration tests are located at `e2e/suite/specs`. The page objects Here is a good introduction to e2e best practices: https://martinfowler.com/bliki/PageObject.html. - `Selector`: A unique identifier that is used from the e2e framework to retrieve an element from the Browser -- `Page`: An abstraction for an object that contains one or more `Selectors` +- `Page`: An abstraction for an object that contains one or more `Selectors` with `visit` function to navigate to the page. +- `Component`: An abstraction for an object that contains one or more `Selectors` but without `visit` function - `Flow`: An abstraction that contains a sequence of actions on one or more `Pages` that can be reused and shared between tests ## Basic example @@ -57,15 +58,13 @@ Now that we added the `aria-label` we suddenly get more information about this p The next step is to create a `Page` representation in our e2e test framework to glue the test with the real implementation using the `pageFactory` function. For that function we can supply a `url` and `selectors` like in the example below: ```typescript -export const Login = pageFactory({ +export const Login = { url: "/login", // used when called from Login.visit() - selectors: { - username: "Username input field", // used when called from Login.username().type('Hello World') - }, -}); + username: "Username input field", // used when called from Login.username().type('Hello World') +}; ``` -The next step is to add the `Login` page to the exported const `Pages` in `packages/grafana-e2e/src/pages/index.ts` so that it appears when we type `e2e.pages` in our IDE. +The next step is to add the `Login` page to the exported const `Pages` in `packages/grafana-e2e-selectors/src/selectors/pages.ts` so that it appears when we type `e2e.pages` in our IDE. ```ecmascript 6 export const Pages = { @@ -81,15 +80,15 @@ Now that we have a `Page` called `Login` in our `Pages` const we can use that to ```jsx harmony
- +
``` -The last step in our example is to use our `Login` page as part of a test. The `pageFactory` function we used before gives us two things: +The last step in our example is to use our `Login` page as part of a test. - The `url` property is used whenever we call the `visit` function and is equivalent to the Cypress function [cy.visit()](https://docs.cypress.io/api/commands/visit.html#Syntax). > Best practice after calling `visit` is to always call `should` on a selector to prevent flaky tests when you try to access an element that isn't ready. For more information, refer to [Commands vs. assertions](https://docs.cypress.io/guides/core-concepts/retry-ability.html#Commands-vs-assertions). -- Any defined selector in the `selectors` property can be accessed from the `Login` page by invoking it. This is equivalent to the result of the Cypress function [cy.get(...)](https://docs.cypress.io/api/commands/get.html#Syntax). +- Any defined selector can be accessed from the `Login` page by invoking it. This is equivalent to the result of the Cypress function [cy.get(...)](https://docs.cypress.io/api/commands/get.html#Syntax). ```ecmascript 6 describe('Login test', () => { @@ -123,17 +122,15 @@ Let's take a look at an example that uses the same `selector` for multiple items Just as before in the basic example we'll start by creating a page abstraction using the `pageFactory` function: ```typescript -export const DataSources = pageFactory({ +export const DataSources = { url: '/datasources', - selectors: { - dataSources: (dataSourceName: string) => `Data source list item ${dataSourceName}`, - }, -}); + dataSources: (dataSourceName: string) => `Data source list item ${dataSourceName}`, +}; ```` You might have noticed that instead of a simple `string` as the `selector`, we're using a `function` that takes a string parameter as an argument and returns a formatted string using the argument. -Just as before we need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e/src/pages/index.ts`. +Just as before we need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e-selectors/src/selectors/pages.ts`. The next step is to use the `dataSources` selector function as in our example below: @@ -142,7 +139,7 @@ The next step is to use the `dataSources` selector function as in our example be {dataSources.map(dataSource => (
  • -
    +
    {dataSource.name}
    diff --git a/e2e/suite1/specs/1-smoketests.spec.ts b/e2e/suite1/specs/1-smoketests.spec.ts index 1afb77dc7da..e3ccd9d4937 100644 --- a/e2e/suite1/specs/1-smoketests.spec.ts +++ b/e2e/suite1/specs/1-smoketests.spec.ts @@ -7,9 +7,7 @@ e2e.scenario({ addScenarioDashBoard: true, skipScenario: false, scenario: () => { - // @todo remove `@ts-ignore` when possible - // @ts-ignore - e2e.getScenarioContext().then(({ lastAddedDashboardUid }) => { + e2e.getScenarioContext().then(({ lastAddedDashboardUid }: any) => { e2e.flows.openDashboard(lastAddedDashboardUid); }); e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); diff --git a/e2e/suite1/specs/queryVariableCrud.spec.ts b/e2e/suite1/specs/queryVariableCrud.spec.ts index 6817091d1d6..f9fa204ca80 100644 --- a/e2e/suite1/specs/queryVariableCrud.spec.ts +++ b/e2e/suite1/specs/queryVariableCrud.spec.ts @@ -8,9 +8,7 @@ e2e.scenario({ addScenarioDashBoard: true, skipScenario: false, scenario: () => { - // @todo remove `@ts-ignore` when possible - // @ts-ignore - e2e.getScenarioContext().then(({ lastAddedDashboardUid }) => { + e2e.getScenarioContext().then(({ lastAddedDashboardUid }: any) => { e2e.flows.openDashboard(lastAddedDashboardUid); }); e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); @@ -253,9 +251,7 @@ const assertAdding3dependantQueryVariablesScenario = (queryVariables: QueryVaria for (let queryVariableIndex = 0; queryVariableIndex < queryVariables.length; queryVariableIndex++) { const { name, label, query, options, selectedOption } = queryVariables[queryVariableIndex]; const asserts = queryVariables.slice(0, queryVariableIndex + 1); - // @todo remove `@ts-ignore` when possible - // @ts-ignore - e2e.getScenarioContext().then(({ lastAddedDataSource }) => { + e2e.getScenarioContext().then(({ lastAddedDataSource }: any) => { createQueryVariable({ dataSourceName: lastAddedDataSource, name, diff --git a/packages/grafana-e2e-selectors/CHANGELOG.md b/packages/grafana-e2e-selectors/CHANGELOG.md new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ b/packages/grafana-e2e-selectors/CHANGELOG.md @@ -0,0 +1,2 @@ + + diff --git a/packages/grafana-e2e-selectors/README.md b/packages/grafana-e2e-selectors/README.md new file mode 100644 index 00000000000..60c35d7b69a --- /dev/null +++ b/packages/grafana-e2e-selectors/README.md @@ -0,0 +1,3 @@ +# Grafana End-to-End Test Selectors library + +> **@grafana/e2e-selectors is currently in ALPHA**. Core API is unstable and can be a subject of breaking changes! diff --git a/packages/grafana-e2e-selectors/api-extractor.json b/packages/grafana-e2e-selectors/api-extractor.json new file mode 100644 index 00000000000..5e96b3b0b3c --- /dev/null +++ b/packages/grafana-e2e-selectors/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.json" +} diff --git a/packages/grafana-e2e-selectors/index.js b/packages/grafana-e2e-selectors/index.js new file mode 100644 index 00000000000..5d1c92525d2 --- /dev/null +++ b/packages/grafana-e2e-selectors/index.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./index.production.js'); +} else { + module.exports = require('./index.development.js'); +} diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json new file mode 100644 index 00000000000..631d3fa2f1d --- /dev/null +++ b/packages/grafana-e2e-selectors/package.json @@ -0,0 +1,50 @@ +{ + "author": "Grafana Labs", + "license": "Apache-2.0", + "name": "@grafana/e2e-selectors", + "version": "7.0.0-pre.0", + "description": "Grafana End-to-End Test Selectors Library", + "keywords": [ + "cli", + "grafana", + "e2e", + "typescript" + ], + "repository": { + "type": "git", + "url": "http://github.com/grafana/grafana.git", + "directory": "packages/grafana-e2e-selectors" + }, + "main": "src/index.ts", + "scripts": { + "build": "grafana-toolkit package:build --scope=e2e-selectors", + "bundle": "rollup -c rollup.config.ts", + "clean": "rimraf ./dist ./compiled", + "docsExtract": "mkdir -p ../../reports/docs && api-extractor run 2>&1 | tee ../../reports/docs/$(basename $(pwd)).log", + "lint": "eslint src/ --ext=.js,.ts,.tsx", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "13.7.7", + "@rollup/plugin-commonjs": "11.0.2", + "@rollup/plugin-node-resolve": "7.1.1", + "@types/rollup-plugin-visualizer": "2.6.0", + "@types/systemjs": "^0.20.6", + "pretty-format": "25.1.0", + "rollup": "2.0.6", + "rollup-plugin-sourcemaps": "0.5.0", + "rollup-plugin-terser": "5.3.0", + "rollup-plugin-typescript2": "0.26.0", + "rollup-plugin-visualizer": "3.3.1", + "ts-loader": "6.2.1", + "ts-node": "8.8.1" + }, + "types": "src/index.ts", + "dependencies": { + "@grafana/tsconfig": "^1.0.0-rc1", + "commander": "5.0.0", + "execa": "4.0.0", + "typescript": "3.7.5", + "yaml": "^1.8.3" + } +} diff --git a/packages/grafana-e2e-selectors/rollup.config.ts b/packages/grafana-e2e-selectors/rollup.config.ts new file mode 100644 index 00000000000..41c1a503345 --- /dev/null +++ b/packages/grafana-e2e-selectors/rollup.config.ts @@ -0,0 +1,25 @@ +import resolve from '@rollup/plugin-node-resolve'; +import sourceMaps from 'rollup-plugin-sourcemaps'; +import { terser } from 'rollup-plugin-terser'; + +const pkg = require('./package.json'); + +const libraryName = pkg.name; + +const buildCjsPackage = ({ env }) => { + return { + input: `compiled/index.js`, + output: [ + { + file: `dist/index.${env}.js`, + name: libraryName, + format: 'cjs', + sourcemap: true, + exports: 'named', + globals: {}, + }, + ], + plugins: [resolve(), sourceMaps(), env === 'production' && terser()], + }; +}; +export default [buildCjsPackage({ env: 'development' }), buildCjsPackage({ env: 'production' })]; diff --git a/packages/grafana-e2e-selectors/src/index.ts b/packages/grafana-e2e-selectors/src/index.ts new file mode 100644 index 00000000000..8c17fa0afdf --- /dev/null +++ b/packages/grafana-e2e-selectors/src/index.ts @@ -0,0 +1,7 @@ +/** + * A library containing the different design components of the Grafana ecosystem. + * + * @packageDocumentation + */ +export * from './selectors'; +export * from './types'; diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts new file mode 100644 index 00000000000..e02f4996444 --- /dev/null +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -0,0 +1,108 @@ +import { Pages } from './pages'; + +export const Components = { + DataSource: { + TestData: { + QueryTab: { + scenarioSelect: 'Test Data Query scenario select', + max: 'TestData max', + min: 'TestData min', + noise: 'TestData noise', + seriesCount: 'TestData series count', + spread: 'TestData spread', + startValue: 'TestData start value', + }, + }, + }, + Panels: { + Panel: { + title: (title: string) => `Panel header title item ${title}`, + headerItems: (item: string) => `Panel header item ${item}`, + }, + Visualization: { + Graph: { + VisualizationTab: { + legendSection: 'Legend section', + }, + Legend: { + legendItemAlias: (name: string) => `gpl alias ${name}`, + showLegendSwitch: 'gpl show legend', + }, + }, + }, + }, + Drawer: { + General: { + title: (title: string) => `Drawer title ${title}`, + expand: 'Drawer expand', + contract: 'Drawer contract', + close: 'Drawer close', + rcContentWrapper: () => '.drawer-content-wrapper', + }, + }, + PanelEditor: { + General: { + content: 'Panel editor content', + }, + OptionsPane: { + content: 'Panel editor option pane content', + close: Pages.Dashboard.Toolbar.toolbarItems('Close options pane'), + open: Pages.Dashboard.Toolbar.toolbarItems('Open options pane'), + select: 'Panel editor option pane select', + }, + // not sure about the naming *DataPane* + DataPane: { + content: 'Panel editor data pane content', + }, + }, + PanelInspector: { + Data: { + content: 'Panel inspector Data content', + }, + Stats: { + content: 'Panel inspector Stats content', + }, + Json: { + content: 'Panel inspector Json content', + }, + Query: { + content: 'Panel inspector Query content', + }, + }, + Tab: { + title: (title: string) => `Tab ${title}`, + active: () => '[class*="-activeTabStyle"]', + }, + QueryTab: { + content: 'Query editor tab content', + queryInspectorButton: 'Query inspector button', + }, + AlertTab: { + content: 'Alert editor tab content', + }, + TransformTab: { + content: 'Transform editor tab content', + }, + QueryEditorToolbarItem: { + button: (title: string) => `QueryEditor toolbar item button ${title}`, + }, + BackButton: { + backArrow: 'Go Back button', + }, + OptionsGroup: { + toggle: (title: string) => `Options group ${title}`, + }, + PluginVisualization: { + item: (title: string) => `Plugin visualization item ${title}`, + current: () => '[class*="-currentVisualizationItem"]', + }, + Select: { + option: 'Select option', + }, + FieldConfigEditor: { + content: 'Field config editor content', + }, + OverridesConfigEditor: { + content: 'Field overrides editor content', + }, +}; diff --git a/packages/grafana-e2e-selectors/src/selectors/index.ts b/packages/grafana-e2e-selectors/src/selectors/index.ts new file mode 100644 index 00000000000..6404693167d --- /dev/null +++ b/packages/grafana-e2e-selectors/src/selectors/index.ts @@ -0,0 +1,8 @@ +import { Pages } from './pages'; +import { Components } from './components'; +import { E2ESelectors } from '../types'; + +export const selectors: { pages: E2ESelectors; components: E2ESelectors } = { + pages: Pages, + components: Components, +}; diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts new file mode 100644 index 00000000000..bfb6feb57ef --- /dev/null +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -0,0 +1,121 @@ +export const Pages = { + Login: { + url: '/login', + username: 'Username input field', + password: 'Password input field', + submit: 'Login button', + skip: 'Skip change password button', + }, + DataSource: { + name: 'Data source settings page name input field', + delete: 'Data source settings page Delete button', + saveAndTest: 'Data source settings page Save and Test button', + alert: 'Data source settings page Alert', + alertMessage: 'Data source settings page Alert message', + }, + DataSources: { + url: '/datasources', + dataSources: (dataSourceName: string) => `Data source list item ${dataSourceName}`, + }, + AddDataSource: { + url: '/datasources/new', + dataSourcePlugins: (pluginName: string) => `Data source plugin item ${pluginName}`, + }, + ConfirmModal: { + delete: 'Confirm Modal Danger Button', + }, + AddDashboard: { + url: '/dashboard/new', + addNewPanel: 'Add new panel', + }, + Dashboard: { + url: (uid: string) => `/d/${uid}`, + Toolbar: { + toolbarItems: (button: string) => `Dashboard navigation bar button ${button}`, + navBar: () => '.navbar', + }, + SubMenu: { + submenuItem: 'Dashboard template variables submenu item', + submenuItemLabels: (item: string) => `Dashboard template variables submenu Label ${item}`, + submenuItemValueDropDownValueLinkTexts: (item: string) => + `Dashboard template variables Variable Value DropDown value link text ${item}`, + submenuItemValueDropDownDropDown: 'Dashboard template variables Variable Value DropDown DropDown', + submenuItemValueDropDownOptionTexts: (item: string) => + `Dashboard template variables Variable Value DropDown option text ${item}`, + }, + Settings: { + General: { + deleteDashBoard: 'Dashboard settings page delete dashboard button', + sectionItems: (item: string) => `Dashboard settings section item ${item}`, + saveDashBoard: 'Dashboard settings aside actions Save button', + saveAsDashBoard: 'Dashboard settings aside actions Save As button', + }, + Variables: { + List: { + addVariableCTA: 'Call to action button Add variable', + newButton: 'Variable editor New variable button', + table: 'Variable editor Table', + tableRowNameFields: (variableName: string) => `Variable editor Table Name field ${variableName}`, + tableRowDefinitionFields: (variableName: string) => `Variable editor Table Definition field ${variableName}`, + tableRowArrowUpButtons: (variableName: string) => `Variable editor Table ArrowUp button ${variableName}`, + tableRowArrowDownButtons: (variableName: string) => `Variable editor Table ArrowDown button ${variableName}`, + tableRowDuplicateButtons: (variableName: string) => `Variable editor Table Duplicate button ${variableName}`, + tableRowRemoveButtons: (variableName: string) => `Variable editor Table Remove button ${variableName}`, + }, + Edit: { + General: { + headerLink: 'Variable editor Header link', + modeLabelNew: 'Variable editor Header mode New', + modeLabelEdit: 'Variable editor Header mode Edit', + generalNameInput: 'Variable editor Form Name field', + generalTypeSelect: 'Variable editor Form Type select', + generalLabelInput: 'Variable editor Form Label field', + generalHideSelect: 'Variable editor Form Hide select', + selectionOptionsMultiSwitch: 'Variable editor Form Multi switch', + selectionOptionsIncludeAllSwitch: 'Variable editor Form IncludeAll switch', + selectionOptionsCustomAllInput: 'Variable editor Form IncludeAll field', + previewOfValuesOption: 'Variable editor Preview of Values option', + addButton: 'Variable editor Add button', + updateButton: 'Variable editor Update button', + }, + QueryVariable: { + queryOptionsDataSourceSelect: 'Variable editor Form Query DataSource select', + queryOptionsRefreshSelect: 'Variable editor Form Query Refresh select', + queryOptionsRegExInput: 'Variable editor Form Query RegEx field', + queryOptionsSortSelect: 'Variable editor Form Query Sort select', + queryOptionsQueryInput: 'Variable editor Form Default Variable Query Editor textarea', + valueGroupsTagsEnabledSwitch: 'Variable editor Form Query UseTags switch', + valueGroupsTagsTagsQueryInput: 'Variable editor Form Query TagsQuery field', + valueGroupsTagsTagsValuesQueryInput: 'Variable editor Form Query TagsValuesQuery field', + }, + ConstantVariable: { + constantOptionsQueryInput: 'Variable editor Form Constant Query field', + }, + }, + }, + }, + }, + Dashboards: { + url: '/dashboards', + dashboards: (title: string) => `Dashboard search item ${title}`, + }, + SaveDashboardAsModal: { + newName: 'Save dashboard title field', + save: 'Save dashboard button', + }, + SaveDashboardModal: { + save: 'Dashboard settings Save Dashboard Modal Save button', + saveVariables: 'Dashboard settings Save Dashboard Modal Save variables checkbox', + saveTimerange: 'Dashboard settings Save Dashboard Modal Save timerange checkbox', + }, + SharePanelModal: { + linkToRenderedImage: 'Link to rendered image', + }, + Explore: { + url: '/explore', + General: { + container: 'Explore', + runButton: 'Run button', + }, + }, +}; diff --git a/packages/grafana-e2e-selectors/src/types/index.ts b/packages/grafana-e2e-selectors/src/types/index.ts new file mode 100644 index 00000000000..8c9698fe1e2 --- /dev/null +++ b/packages/grafana-e2e-selectors/src/types/index.ts @@ -0,0 +1 @@ +export * from './selectors'; diff --git a/packages/grafana-e2e-selectors/src/types/selectors.ts b/packages/grafana-e2e-selectors/src/types/selectors.ts new file mode 100644 index 00000000000..e58a9251cac --- /dev/null +++ b/packages/grafana-e2e-selectors/src/types/selectors.ts @@ -0,0 +1,15 @@ +export type StringSelector = string; +export type FunctionSelector = (id: string) => string; +export type CssSelector = () => string; + +export interface Selectors { + [key: string]: StringSelector | FunctionSelector | CssSelector | UrlSelector | Selectors; +} + +export type E2ESelectors = { + [P in keyof S]: S[P]; +}; + +export interface UrlSelector extends Selectors { + url: string | FunctionSelector; +} diff --git a/packages/grafana-e2e-selectors/tsconfig.build.json b/packages/grafana-e2e-selectors/tsconfig.build.json new file mode 100644 index 00000000000..9ec189c28ea --- /dev/null +++ b/packages/grafana-e2e-selectors/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "exclude": ["dist", "node_modules", "**/*.test.ts*"], + "extends": "./tsconfig.json" +} diff --git a/packages/grafana-e2e-selectors/tsconfig.json b/packages/grafana-e2e-selectors/tsconfig.json new file mode 100644 index 00000000000..a0cb12dae6b --- /dev/null +++ b/packages/grafana-e2e-selectors/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "declarationDir": "dist", + "outDir": "compiled", + "rootDirs": ["."], + "typeRoots": ["node_modules/@types"] + }, + "exclude": ["dist", "node_modules"], + "extends": "@grafana/tsconfig", + "include": ["src/**/*.ts"] +} diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index 7095c451792..9192a7f62a9 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -31,7 +31,6 @@ }, "devDependencies": { "@cypress/webpack-preprocessor": "4.1.3", - "@grafana/tsconfig": "^1.0.0-rc1", "@rollup/plugin-commonjs": "11.0.2", "@rollup/plugin-node-resolve": "7.1.1", "@types/node": "13.7.7", @@ -43,19 +42,18 @@ "rollup-plugin-typescript2": "0.26.0", "rollup-plugin-visualizer": "3.3.1", "ts-loader": "6.2.1", - "typescript": "3.7.5", "ts-node": "8.8.1" }, "types": "src/index.ts", "dependencies": { - "@cypress/webpack-preprocessor": "4.1.1", "@grafana/tsconfig": "^1.0.0-rc1", + "@grafana/e2e-selectors": "7.0.0-pre.0", "blink-diff": "1.0.13", "commander": "5.0.0", "cypress": "3.7.0", "execa": "4.0.0", "ts-loader": "6.2.1", - "typescript": "3.7.2", + "typescript": "3.7.5", "yaml": "^1.8.3" } } diff --git a/packages/grafana-e2e/rollup.config.ts b/packages/grafana-e2e/rollup.config.ts index df8ba1db0ab..341c18b8719 100644 --- a/packages/grafana-e2e/rollup.config.ts +++ b/packages/grafana-e2e/rollup.config.ts @@ -16,6 +16,7 @@ const buildCjsPackage = ({ env }) => ({ exports: 'named', globals: {}, }, + external: ['@grafana/e2e-selectors'], plugins: [ copy({ flatten: false, diff --git a/packages/grafana-e2e/src/components/index.ts b/packages/grafana-e2e/src/components/index.ts index 5332e57f9b7..e69de29bb2d 100644 --- a/packages/grafana-e2e/src/components/index.ts +++ b/packages/grafana-e2e/src/components/index.ts @@ -1,129 +0,0 @@ -import { TestData } from '../pages/testdata'; -import { Panel } from '../pages/panel'; -import { Graph } from '../pages/graph'; -import { componentFactory } from '../support'; -import { Dashboard } from '../pages/dashboard'; - -export const Components = { - DataSource: { - TestData, - }, - Panels: { - Panel, - Visualization: { - Graph, - }, - }, - Drawer: { - General: componentFactory({ - selectors: { - title: (title: string) => `Drawer title ${title}`, - expand: 'Drawer expand', - contract: 'Drawer contract', - close: 'Drawer close', - rcContentWrapper: () => '.drawer-content-wrapper', - }, - }), - }, - PanelEditor: { - General: componentFactory({ - selectors: { - content: 'Panel editor content', - }, - }), - OptionsPane: componentFactory({ - selectors: { - content: 'Panel editor option pane content', - close: Dashboard.selectors.toolbarItems('Close options pane'), - open: Dashboard.selectors.toolbarItems('Open options pane'), - select: 'Panel editor option pane select', - }, - }), - // not sure about the naming *DataPane* - DataPane: componentFactory({ - selectors: { - content: 'Panel editor data pane content', - }, - }), - }, - PanelInspector: { - Data: componentFactory({ - selectors: { - content: 'Panel inspector Data content', - }, - }), - Stats: componentFactory({ - selectors: { - content: 'Panel inspector Stats content', - }, - }), - Json: componentFactory({ - selectors: { - content: 'Panel inspector Json content', - }, - }), - Query: componentFactory({ - selectors: { - content: 'Panel inspector Query content', - }, - }), - }, - Tab: componentFactory({ - selectors: { - title: (title: string) => `Tab ${title}`, - active: () => '[class*="-activeTabStyle"]', - }, - }), - QueryTab: componentFactory({ - selectors: { - content: 'Query editor tab content', - queryInspectorButton: 'Query inspector button', - }, - }), - AlertTab: componentFactory({ - selectors: { - content: 'Alert editor tab content', - }, - }), - TransformTab: componentFactory({ - selectors: { - content: 'Transform editor tab content', - }, - }), - QueryEditorToolbarItem: componentFactory({ - selectors: { - button: (title: string) => `QueryEditor toolbar item button ${title}`, - }, - }), - BackButton: componentFactory({ - selectors: { - backArrow: 'Go Back button', - }, - }), - OptionsGroup: componentFactory({ - selectors: { - toggle: (title: string) => `Options group ${title}`, - }, - }), - PluginVisualization: componentFactory({ - selectors: { - item: (title: string) => `Plugin visualization item ${title}`, - current: () => '[class*="-currentVisualizationItem"]', - }, - }), - Select: componentFactory({ - selectors: { - option: 'Select option', - }, - }), - FieldConfigEditor: componentFactory({ - selectors: { - content: 'Field config editor content', - }, - }), - OverridesConfigEditor: componentFactory({ - selectors: { - content: 'Field overrides editor content', - }, - }), -}; diff --git a/packages/grafana-e2e/src/flows/addPanel.ts b/packages/grafana-e2e/src/flows/addPanel.ts index 20eec4d78e5..1304497fea4 100644 --- a/packages/grafana-e2e/src/flows/addPanel.ts +++ b/packages/grafana-e2e/src/flows/addPanel.ts @@ -14,9 +14,7 @@ const DEFAULT_ADD_PANEL_CONFIG: AddPanelConfig = { export const addPanel = (config?: Partial) => { const { dataSourceName, queriesForm } = { ...DEFAULT_ADD_PANEL_CONFIG, ...config }; - // @todo remove `@ts-ignore` when possible - // @ts-ignore - getScenarioContext().then(({ lastAddedDashboardUid }) => { + getScenarioContext().then(({ lastAddedDashboardUid }: any) => { e2e.flows.openDashboard(lastAddedDashboardUid); e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); e2e.pages.AddDashboard.addNewPanel().click(); diff --git a/packages/grafana-e2e/src/flows/openPanelMenuItem.ts b/packages/grafana-e2e/src/flows/openPanelMenuItem.ts index 2bc253de888..20c80df55e2 100644 --- a/packages/grafana-e2e/src/flows/openPanelMenuItem.ts +++ b/packages/grafana-e2e/src/flows/openPanelMenuItem.ts @@ -1,4 +1,4 @@ -import { e2e } from '../noTypeCheck'; +import { e2e } from '../index'; export enum PanelMenuItems { Edit = 'Edit', diff --git a/packages/grafana-e2e/src/index.ts b/packages/grafana-e2e/src/index.ts index d1000985f06..1d77e9408df 100644 --- a/packages/grafana-e2e/src/index.ts +++ b/packages/grafana-e2e/src/index.ts @@ -3,4 +3,24 @@ * * @packageDocumentation */ -export { e2e } from './noTypeCheck'; +import { e2eScenario, ScenarioArguments } from './support/scenario'; +import { Flows } from './flows'; +import { getScenarioContext, setScenarioContext } from './support/scenarioContext'; +import { e2eFactory } from './support'; +import { Pages } from '@grafana/e2e-selectors/src/selectors/pages'; +import { Components } from '@grafana/e2e-selectors/src/selectors/components'; + +const e2eObject = { + env: (args: string) => Cypress.env(args), + config: () => Cypress.config(), + blobToBase64String: (blob: any) => Cypress.Blob.blobToBase64String(blob), + imgSrcToBlob: (url: string) => Cypress.Blob.imgSrcToBlob(url), + scenario: (args: ScenarioArguments) => e2eScenario(args), + pages: e2eFactory({ selectors: Pages }), + components: e2eFactory({ selectors: Components }), + flows: Flows, + getScenarioContext, + setScenarioContext, +}; + +export const e2e: (() => Cypress.cy) & typeof e2eObject = Object.assign(() => cy, e2eObject); diff --git a/packages/grafana-e2e/src/noTypeCheck.ts b/packages/grafana-e2e/src/noTypeCheck.ts deleted file mode 100644 index 6754d61f0c9..00000000000 --- a/packages/grafana-e2e/src/noTypeCheck.ts +++ /dev/null @@ -1,28 +0,0 @@ -// @ts-nocheck -// importing the e2e package in Grafana will cause transpile errors because -// Cypress is an unknown type. Adding the Cypress types would overwrite all jest test types like -// toBe, toEqual and so forth. That's why this file is not type checked and will be so until we -// can solve the above mentioned issue with Cypress/Jest. -import { e2eScenario, ScenarioArguments } from './support/scenario'; -import { Pages } from './pages'; -import { Components } from './components'; -import { Flows } from './flows'; -import { getScenarioContext, setScenarioContext } from './support/scenarioContext'; - -export type SelectorFunction = (text?: string) => Cypress.Chainable>; -export type VisitFunction = (args?: string) => Cypress.Chainable; - -const e2eObject = { - env: (args: string) => Cypress.env(args), - config: () => Cypress.config(), - blobToBase64String: (blob: any) => Cypress.Blob.blobToBase64String(blob), - imgSrcToBlob: (url: string) => Cypress.Blob.imgSrcToBlob(url), - scenario: (args: ScenarioArguments) => e2eScenario(args), - pages: Pages, - components: Components, - flows: Flows, - getScenarioContext, - setScenarioContext, -}; - -export const e2e: (() => Cypress.cy) & typeof e2eObject = Object.assign(() => cy, e2eObject); diff --git a/packages/grafana-e2e/src/pages/addDashboard.ts b/packages/grafana-e2e/src/pages/addDashboard.ts deleted file mode 100644 index 881eb590ff6..00000000000 --- a/packages/grafana-e2e/src/pages/addDashboard.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { pageFactory } from '../support'; - -export const AddDashboard = pageFactory({ - url: '/dashboard/new', - selectors: { - addNewPanel: 'Add new panel', - }, -}); diff --git a/packages/grafana-e2e/src/pages/addDataSource.ts b/packages/grafana-e2e/src/pages/addDataSource.ts deleted file mode 100644 index ad8682b40d4..00000000000 --- a/packages/grafana-e2e/src/pages/addDataSource.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { pageFactory } from '../support'; - -export const AddDataSource = pageFactory({ - url: '/datasources/new', - selectors: { - dataSourcePlugins: (pluginName: string) => `Data source plugin item ${pluginName}`, - }, -}); diff --git a/packages/grafana-e2e/src/pages/confirmModal.ts b/packages/grafana-e2e/src/pages/confirmModal.ts deleted file mode 100644 index 30d7bca551e..00000000000 --- a/packages/grafana-e2e/src/pages/confirmModal.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { pageFactory } from '../support'; - -export const ConfirmModal = pageFactory({ - url: '', - selectors: { - delete: 'Confirm Modal Danger Button', - }, -}); diff --git a/packages/grafana-e2e/src/pages/dashboard.ts b/packages/grafana-e2e/src/pages/dashboard.ts deleted file mode 100644 index a6111d2dce2..00000000000 --- a/packages/grafana-e2e/src/pages/dashboard.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { pageFactory } from '../support'; - -export const Dashboard = pageFactory({ - url: (uid: string) => `/d/${uid}`, - selectors: { - toolbarItems: (button: string) => `Dashboard navigation bar button ${button}`, - navBar: () => '.navbar', - }, -}); diff --git a/packages/grafana-e2e/src/pages/dashboardSettings.ts b/packages/grafana-e2e/src/pages/dashboardSettings.ts deleted file mode 100644 index 5a4c2c52434..00000000000 --- a/packages/grafana-e2e/src/pages/dashboardSettings.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { pageFactory } from '../support'; - -export const DashboardSettings = pageFactory({ - url: '', - selectors: { - deleteDashBoard: 'Dashboard settings page delete dashboard button', - sectionItems: (item: string) => `Dashboard settings section item ${item}`, - saveDashBoard: 'Dashboard settings aside actions Save button', - saveAsDashBoard: 'Dashboard settings aside actions Save As button', - }, -}); diff --git a/packages/grafana-e2e/src/pages/dashboards.ts b/packages/grafana-e2e/src/pages/dashboards.ts deleted file mode 100644 index 12bebbdb084..00000000000 --- a/packages/grafana-e2e/src/pages/dashboards.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { pageFactory } from '../support'; - -export const Dashboards = pageFactory({ - url: '/dashboards', - selectors: { - dashboards: (title: string) => `Dashboard search item ${title}`, - }, -}); diff --git a/packages/grafana-e2e/src/pages/datasource.ts b/packages/grafana-e2e/src/pages/datasource.ts deleted file mode 100644 index 2a10e63d618..00000000000 --- a/packages/grafana-e2e/src/pages/datasource.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { pageFactory } from '../support'; - -export const DataSource = pageFactory({ - url: '', - selectors: { - name: 'Data source settings page name input field', - delete: 'Data source settings page Delete button', - saveAndTest: 'Data source settings page Save and Test button', - alert: 'Data source settings page Alert', - alertMessage: 'Data source settings page Alert message', - }, -}); diff --git a/packages/grafana-e2e/src/pages/datasources.ts b/packages/grafana-e2e/src/pages/datasources.ts deleted file mode 100644 index e923bb0136b..00000000000 --- a/packages/grafana-e2e/src/pages/datasources.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { pageFactory } from '../support'; - -export const DataSources = pageFactory({ - url: '/datasources', - selectors: { - dataSources: (dataSourceName: string) => `Data source list item ${dataSourceName}`, - }, -}); diff --git a/packages/grafana-e2e/src/pages/explore.ts b/packages/grafana-e2e/src/pages/explore.ts deleted file mode 100644 index f8223797334..00000000000 --- a/packages/grafana-e2e/src/pages/explore.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { pageFactory } from '../support'; - -export const Explore = pageFactory({ - url: '/explore', - selectors: { - container: 'Explore', - runButton: 'Run button', - }, -}); diff --git a/packages/grafana-e2e/src/pages/graph/index.ts b/packages/grafana-e2e/src/pages/graph/index.ts deleted file mode 100644 index 5ef19a026c4..00000000000 --- a/packages/grafana-e2e/src/pages/graph/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { VisualizationTab } from './visualizationTab'; -import { pageFactory } from '../../support'; - -export const Graph = { - VisualizationTab, - Legend: pageFactory({ - url: '', - selectors: { - legendItemAlias: (name: string) => `gpl alias ${name}`, - showLegendSwitch: 'gpl show legend', - }, - }), -}; diff --git a/packages/grafana-e2e/src/pages/graph/visualizationTab.ts b/packages/grafana-e2e/src/pages/graph/visualizationTab.ts deleted file mode 100644 index f058e917d94..00000000000 --- a/packages/grafana-e2e/src/pages/graph/visualizationTab.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { pageFactory } from '../../support'; - -export const VisualizationTab = pageFactory({ - url: '', - selectors: { - xAxisSection: 'X-Axis section', - axesSection: 'Axes section', - legendSection: 'Legend section', - displaySection: 'Display section', - }, -}); diff --git a/packages/grafana-e2e/src/pages/index.ts b/packages/grafana-e2e/src/pages/index.ts deleted file mode 100644 index 54067081345..00000000000 --- a/packages/grafana-e2e/src/pages/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Login } from './login'; -import { AddDataSource } from './addDataSource'; -import { DataSource } from './datasource'; -import { DataSources } from './datasources'; -import { ConfirmModal } from './confirmModal'; -import { AddDashboard } from './addDashboard'; -import { Dashboard } from './dashboard'; -import { SaveDashboardAsModal } from './saveDashboardAsModal'; -import { Dashboards } from './dashboards'; -import { DashboardSettings } from './dashboardSettings'; -import { Explore } from './explore'; -import { SaveDashboardModal } from './saveDashboardModal'; -import { SharePanelModal } from './sharePanelModal'; -import { ConstantVariable, QueryVariable, VariableGeneral, Variables, VariablesSubMenu } from './variables'; - -export const Pages = { - Login, - DataSource, - DataSources, - AddDataSource, - ConfirmModal, - AddDashboard, - Dashboard: { - visit: (uid: string) => Dashboard.visit(uid), - Toolbar: Dashboard, - SubMenu: VariablesSubMenu, - Settings: { - General: DashboardSettings, - Variables: { - List: Variables, - Edit: { - General: VariableGeneral, - QueryVariable: QueryVariable, - ConstantVariable: ConstantVariable, - }, - }, - }, - }, - Dashboards, - SaveDashboardAsModal, - SaveDashboardModal, - SharePanelModal, - Explore: { - visit: () => Explore.visit(), - General: Explore, - }, -}; diff --git a/packages/grafana-e2e/src/pages/login.ts b/packages/grafana-e2e/src/pages/login.ts deleted file mode 100644 index c17f70ab8ce..00000000000 --- a/packages/grafana-e2e/src/pages/login.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { pageFactory } from '../support'; - -export const Login = pageFactory({ - url: '/login', - selectors: { - username: 'Username input field', - password: 'Password input field', - submit: 'Login button', - skip: 'Skip change password button', - }, -}); diff --git a/packages/grafana-e2e/src/pages/panel.ts b/packages/grafana-e2e/src/pages/panel.ts deleted file mode 100644 index 6411e139db0..00000000000 --- a/packages/grafana-e2e/src/pages/panel.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { pageFactory } from '../support'; - -export const Panel = pageFactory({ - url: '', - selectors: { - title: (title: string) => `Panel header title item ${title}`, - headerItems: (item: string) => `Panel header item ${item}`, - }, -}); diff --git a/packages/grafana-e2e/src/pages/saveDashboardAsModal.ts b/packages/grafana-e2e/src/pages/saveDashboardAsModal.ts deleted file mode 100644 index a24660a2a15..00000000000 --- a/packages/grafana-e2e/src/pages/saveDashboardAsModal.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { pageFactory } from '../support'; - -export const SaveDashboardAsModal = pageFactory({ - url: '', - selectors: { - newName: 'Save dashboard title field', - save: 'Save dashboard button', - }, -}); diff --git a/packages/grafana-e2e/src/pages/saveDashboardModal.ts b/packages/grafana-e2e/src/pages/saveDashboardModal.ts deleted file mode 100644 index aafd837e71b..00000000000 --- a/packages/grafana-e2e/src/pages/saveDashboardModal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { pageFactory } from '../support'; - -export const SaveDashboardModal = pageFactory({ - url: '', - selectors: { - save: 'Dashboard settings Save Dashboard Modal Save button', - saveVariables: 'Dashboard settings Save Dashboard Modal Save variables checkbox', - saveTimerange: 'Dashboard settings Save Dashboard Modal Save timerange checkbox', - }, -}); diff --git a/packages/grafana-e2e/src/pages/sharePanelModal.ts b/packages/grafana-e2e/src/pages/sharePanelModal.ts deleted file mode 100644 index eb2951125e5..00000000000 --- a/packages/grafana-e2e/src/pages/sharePanelModal.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { pageFactory } from '../support'; - -export const SharePanelModal = pageFactory({ - url: '', - selectors: { - linkToRenderedImage: 'Link to rendered image', - }, -}); diff --git a/packages/grafana-e2e/src/pages/testdata/index.ts b/packages/grafana-e2e/src/pages/testdata/index.ts deleted file mode 100644 index b103702bbc3..00000000000 --- a/packages/grafana-e2e/src/pages/testdata/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { QueryTab } from './queryTab'; - -export const TestData = { - QueryTab, -}; diff --git a/packages/grafana-e2e/src/pages/testdata/queryTab.ts b/packages/grafana-e2e/src/pages/testdata/queryTab.ts deleted file mode 100644 index dd8f6afe41e..00000000000 --- a/packages/grafana-e2e/src/pages/testdata/queryTab.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { componentFactory } from '../../support'; - -export const QueryTab = componentFactory({ - selectors: { - scenarioSelect: 'Test Data Query scenario select', - max: 'TestData max', - min: 'TestData min', - noise: 'TestData noise', - seriesCount: 'TestData series count', - spread: 'TestData spread', - startValue: 'TestData start value', - }, -}); diff --git a/packages/grafana-e2e/src/pages/variables.ts b/packages/grafana-e2e/src/pages/variables.ts deleted file mode 100644 index a2979de9eb5..00000000000 --- a/packages/grafana-e2e/src/pages/variables.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { pageFactory } from '../support'; - -export const Variables = pageFactory({ - url: '', - selectors: { - addVariableCTA: 'Call to action button Add variable', - newButton: 'Variable editor New variable button', - table: 'Variable editor Table', - tableRowNameFields: (variableName: string) => `Variable editor Table Name field ${variableName}`, - tableRowDefinitionFields: (variableName: string) => `Variable editor Table Definition field ${variableName}`, - tableRowArrowUpButtons: (variableName: string) => `Variable editor Table ArrowUp button ${variableName}`, - tableRowArrowDownButtons: (variableName: string) => `Variable editor Table ArrowDown button ${variableName}`, - tableRowDuplicateButtons: (variableName: string) => `Variable editor Table Duplicate button ${variableName}`, - tableRowRemoveButtons: (variableName: string) => `Variable editor Table Remove button ${variableName}`, - }, -}); - -export const VariablesSubMenu = pageFactory({ - url: '', - selectors: { - submenuItem: 'Dashboard template variables submenu item', - submenuItemLabels: (item: string) => `Dashboard template variables submenu Label ${item}`, - submenuItemValueDropDownValueLinkTexts: (item: string) => - `Dashboard template variables Variable Value DropDown value link text ${item}`, - submenuItemValueDropDownDropDown: 'Dashboard template variables Variable Value DropDown DropDown', - submenuItemValueDropDownOptionTexts: (item: string) => - `Dashboard template variables Variable Value DropDown option text ${item}`, - }, -}); - -export const VariableGeneral = pageFactory({ - url: '', - selectors: { - headerLink: 'Variable editor Header link', - modeLabelNew: 'Variable editor Header mode New', - modeLabelEdit: 'Variable editor Header mode Edit', - generalNameInput: 'Variable editor Form Name field', - generalTypeSelect: 'Variable editor Form Type select', - generalLabelInput: 'Variable editor Form Label field', - generalHideSelect: 'Variable editor Form Hide select', - selectionOptionsMultiSwitch: 'Variable editor Form Multi switch', - selectionOptionsIncludeAllSwitch: 'Variable editor Form IncludeAll switch', - selectionOptionsCustomAllInput: 'Variable editor Form IncludeAll field', - previewOfValuesOption: 'Variable editor Preview of Values option', - addButton: 'Variable editor Add button', - updateButton: 'Variable editor Update button', - }, -}); - -export const QueryVariable = pageFactory({ - url: '', - selectors: { - queryOptionsDataSourceSelect: 'Variable editor Form Query DataSource select', - queryOptionsRefreshSelect: 'Variable editor Form Query Refresh select', - queryOptionsRegExInput: 'Variable editor Form Query RegEx field', - queryOptionsSortSelect: 'Variable editor Form Query Sort select', - queryOptionsQueryInput: 'Variable editor Form Default Variable Query Editor textarea', - valueGroupsTagsEnabledSwitch: 'Variable editor Form Query UseTags switch', - valueGroupsTagsTagsQueryInput: 'Variable editor Form Query TagsQuery field', - valueGroupsTagsTagsValuesQueryInput: 'Variable editor Form Query TagsValuesQuery field', - }, -}); - -export const ConstantVariable = pageFactory({ - url: '', - selectors: { - constantOptionsQueryInput: 'Variable editor Form Constant Query field', - }, -}); diff --git a/packages/grafana-e2e/src/support/scenario.ts b/packages/grafana-e2e/src/support/scenario.ts index 33baee3e469..a6d6962145e 100644 --- a/packages/grafana-e2e/src/support/scenario.ts +++ b/packages/grafana-e2e/src/support/scenario.ts @@ -18,20 +18,10 @@ export const e2eScenario = ({ addScenarioDataSource = false, addScenarioDashBoard = false, }: ScenarioArguments) => { - // when we started to use import { e2e } from '@grafana/e2e'; in grafana/ui components - // then type checking @grafana/run-time started to fail with - // Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`. - // Haven't investigated deeper why this happens yet so adding ts-ignore as temporary solution - // @todo remove `@ts-ignore` when possible - // @ts-ignore describe(describeName, () => { if (skipScenario) { - // @todo remove `@ts-ignore` when possible - // @ts-ignore it.skip(itName, () => scenario()); } else { - // @todo remove `@ts-ignore` when possible - // @ts-ignore beforeEach(() => { Flows.login('admin', 'admin'); if (addScenarioDataSource) { @@ -42,12 +32,8 @@ export const e2eScenario = ({ } }); - // @todo remove `@ts-ignore` when possible - // @ts-ignore afterEach(() => { - // @todo remove `@ts-ignore` when possible - // @ts-ignore - getScenarioContext().then(({ lastAddedDashboardUid, lastAddedDataSource }) => { + getScenarioContext().then(({ lastAddedDashboardUid, lastAddedDataSource }: any) => { if (lastAddedDataSource) { Flows.deleteDataSource(lastAddedDataSource); } @@ -58,8 +44,6 @@ export const e2eScenario = ({ }); }); - // @todo remove `@ts-ignore` when possible - // @ts-ignore it(itName, () => scenario()); } }); diff --git a/packages/grafana-e2e/src/support/types.ts b/packages/grafana-e2e/src/support/types.ts index e5417a25569..2d0000526f9 100644 --- a/packages/grafana-e2e/src/support/types.ts +++ b/packages/grafana-e2e/src/support/types.ts @@ -1,76 +1,99 @@ +import { CssSelector, FunctionSelector, Selectors, StringSelector, UrlSelector } from '@grafana/e2e-selectors'; +import { e2e } from '../index'; import { Selector } from './selector'; import { fromBaseUrl } from './url'; -import { e2e } from '../index'; -import { SelectorFunction, VisitFunction } from '../noTypeCheck'; -export type Selectors = Record; -export type SelectorFunctions = { [P in keyof S]: SelectorFunction }; +export type VisitFunction = (args?: string) => Cypress.Chainable; +export type E2EVisit = { visit: VisitFunction }; +export type E2EFunction = (text?: string) => Cypress.Chainable>; -export type Page = SelectorFunctions & { - selectors: S; - visit: VisitFunction; +export type TypeSelectors = S extends StringSelector + ? E2EFunction + : S extends FunctionSelector + ? E2EFunction + : S extends CssSelector + ? E2EFunction + : S extends UrlSelector + ? E2EVisit & Omit, 'url'> + : S extends Record + ? E2EFunctions + : S; + +export type E2EFunctions = { + [P in keyof S]: TypeSelectors; }; -export interface PageFactoryArgs { - selectors: S; - url?: string | Function; -} -export const pageFactory = ({ url, selectors }: PageFactoryArgs): Page => { - const visit = (args?: string) => { - if (!url) { - return e2e().visit(''); - } +export type E2EObjects = E2EFunctions; - let parsedUrl = ''; - if (typeof url === 'string') { - parsedUrl = fromBaseUrl(url); - } +export type E2EFactoryArgs = { selectors: S }; - if (typeof url === 'function' && args) { - parsedUrl = fromBaseUrl(url(args)); - } - - e2e().logToConsole('Visiting', parsedUrl); - return e2e().visit(parsedUrl); - }; - const pageObjects: SelectorFunctions = {} as SelectorFunctions; +const processSelectors = (e2eObjects: E2EFunctions, selectors: S): E2EFunctions => { + const logOutput = (data: any) => e2e().logToConsole('Retrieving Selector:', data); const keys = Object.keys(selectors); - - keys.forEach(key => { + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; const value = selectors[key]; + + if (key === 'url') { + // @ts-ignore + e2eObjects['visit'] = (args?: string) => { + let parsedUrl = ''; + if (typeof value === 'string') { + parsedUrl = fromBaseUrl(value); + } + + if (typeof value === 'function' && args) { + parsedUrl = fromBaseUrl(value(args)); + } + + e2e().logToConsole('Visiting', parsedUrl); + return e2e().visit(parsedUrl); + }; + + continue; + } + if (typeof value === 'string') { // @ts-ignore - pageObjects[key] = () => { - e2e().logToConsole('Retrieving Selector:', value); + e2eObjects[key] = () => { + logOutput(value); return e2e().get(Selector.fromAriaLabel(value)); }; + + continue; } + if (typeof value === 'function') { // @ts-ignore - pageObjects[key] = (text?: string) => { + e2eObjects[key] = (text?: string) => { if (!text) { - const selector = value(); - e2e().logToConsole('Retrieving Selector:', selector); + const selector = value((undefined as unknown) as string); + + logOutput(selector); return e2e().get(selector); } + const selector = value(text); - e2e().logToConsole('Retrieving Selector:', selector); + + logOutput(selector); return e2e().get(Selector.fromAriaLabel(selector)); }; + + continue; } - }); - return { - visit, - ...pageObjects, - selectors, - }; + if (typeof value === 'object') { + // @ts-ignore + e2eObjects[key] = processSelectors({}, value); + } + } + + return e2eObjects; }; -type Component = Omit, 'visit'>; -type ComponentFactoryArgs = Omit, 'url'>; +export const e2eFactory = ({ selectors }: E2EFactoryArgs): E2EObjects => { + const e2eObjects: E2EFunctions = {} as E2EFunctions; + processSelectors(e2eObjects, selectors); -export const componentFactory = (args: ComponentFactoryArgs): Component => { - const { visit, ...rest } = pageFactory(args); - return rest; + return { ...e2eObjects }; }; diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index 2a3ad860cb7..b84ce961117 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -16,7 +16,7 @@ import { closeMilestoneTask } from './tasks/closeMilestone'; import { pluginDevTask } from './tasks/plugin.dev'; import { githubPublishTask } from './tasks/plugin.utils'; import { pluginUpdateTask } from './tasks/plugin.update'; -import { ciBuildPluginTask, ciBuildPluginDocsTask, ciPackagePluginTask, ciPluginReportTask } from './tasks/plugin.ci'; +import { ciBuildPluginDocsTask, ciBuildPluginTask, ciPackagePluginTask, ciPluginReportTask } from './tasks/plugin.ci'; import { buildPackageTask } from './tasks/package.build'; import { pluginCreateTask } from './tasks/plugin.create'; import { bundleManagedTask } from './tasks/plugin/bundle.managed'; @@ -41,7 +41,7 @@ export const run = (includeInternalScripts = false) => { program .command('package:build') - .option('-s, --scope ', 'packages=[data|runtime|ui|toolkit]') + .option('-s, --scope ', 'packages=[data|runtime|ui|toolkit|e2e|e2e-selectors]') .description('Builds @grafana/* package to packages/grafana-*/dist') .action(async cmd => { await execTask(buildPackageTask)({ diff --git a/packages/grafana-ui/.eslintrc b/packages/grafana-ui/.eslintrc index bd11a76bc35..44ec986f8b9 100644 --- a/packages/grafana-ui/.eslintrc +++ b/packages/grafana-ui/.eslintrc @@ -1,6 +1,6 @@ { "extends": ["@grafana/eslint-config"], "rules": { - "no-restricted-imports": [2, "^@grafana/runtime.*", "^@grafana/ui.*"] + "no-restricted-imports": [2, "^@grafana/runtime.*", "^@grafana/ui.*", "^@grafana/e2e.*"] } } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 0daa8ddd6e0..a542696fc81 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -29,6 +29,7 @@ "dependencies": { "@emotion/core": "^10.0.27", "@grafana/data": "7.0.0-pre.0", + "@grafana/e2e-selectors": "7.0.0-pre.0", "@grafana/slate-react": "0.22.9-grafana", "@grafana/tsconfig": "^1.0.0-rc1", "@iconscout/react-unicons": "^1.0.0", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index 6d99f545079..c364ea7987e 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -24,7 +24,7 @@ const buildCjsPackage = ({ env }) => { }, }, ], - external: ['react', 'react-dom', '@grafana/data', 'moment'], + external: ['react', 'react-dom', '@grafana/data', 'moment', '@grafana/e2e-selectors'], plugins: [ commonjs({ include: /node_modules/, diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index 4fe13860697..7ec37aa2521 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -2,10 +2,11 @@ import React, { CSSProperties, FC, ReactNode, useState } from 'react'; import { GrafanaTheme } from '@grafana/data'; import RcDrawer from 'rc-drawer'; import { css } from 'emotion'; +import { selectors } from '@grafana/e2e-selectors'; + import CustomScrollbar from '../CustomScrollbar/CustomScrollbar'; import { IconButton } from '../IconButton/IconButton'; import { stylesFactory, useTheme } from '../../themes'; -import { e2e } from '@grafana/e2e'; export interface Props { children: ReactNode; @@ -96,8 +97,8 @@ export const Drawer: FC = ({ className={drawerStyles.drawer} aria-label={ typeof title === 'string' - ? e2e.components.Drawer.General.selectors.title(title) - : e2e.components.Drawer.General.selectors.title('no title') + ? selectors.components.Drawer.General.title(title) + : selectors.components.Drawer.General.title('no title') } > {typeof title === 'string' && ( @@ -109,7 +110,7 @@ export const Drawer: FC = ({ size="xl" onClick={() => setIsExpanded(true)} surface="header" - aria-label={e2e.components.Drawer.General.selectors.expand} + aria-label={selectors.components.Drawer.General.expand} /> )} {expandable && isExpanded && ( @@ -118,7 +119,7 @@ export const Drawer: FC = ({ size="xl" onClick={() => setIsExpanded(false)} surface="header" - aria-label={e2e.components.Drawer.General.selectors.contract} + aria-label={selectors.components.Drawer.General.contract} /> )} = ({ size="xl" onClick={onClose} surface="header" - aria-label={e2e.components.Drawer.General.selectors.close} + aria-label={selectors.components.Drawer.General.close} />
    diff --git a/packages/grafana-ui/src/components/Tabs/Tab.tsx b/packages/grafana-ui/src/components/Tabs/Tab.tsx index 32042730c4c..f289e02ba02 100644 --- a/packages/grafana-ui/src/components/Tabs/Tab.tsx +++ b/packages/grafana-ui/src/components/Tabs/Tab.tsx @@ -1,11 +1,12 @@ import React, { FC } from 'react'; import { css, cx } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + import { Icon } from '../Icon/Icon'; import { IconName } from '../../types'; import { stylesFactory, useTheme } from '../../themes'; import { Counter } from './Counter'; -import { e2e } from '@grafana/e2e'; export interface TabProps { label: string; @@ -69,7 +70,7 @@ export const Tab: FC = ({ label, active, icon, onChangeTab, counter })
  • {icon && } {label} diff --git a/public/app/core/components/BackButton/BackButton.tsx b/public/app/core/components/BackButton/BackButton.tsx index 5ad54ed2701..62ed6e2ff44 100644 --- a/public/app/core/components/BackButton/BackButton.tsx +++ b/public/app/core/components/BackButton/BackButton.tsx @@ -1,6 +1,6 @@ import React, { ButtonHTMLAttributes } from 'react'; import { IconButton } from '@grafana/ui'; -import { e2e } from '@grafana/e2e'; +import { selectors } from '@grafana/e2e-selectors'; export interface Props extends ButtonHTMLAttributes { surface: 'dashboard' | 'panel' | 'header'; @@ -14,7 +14,7 @@ export const BackButton: React.FC = ({ surface, onClick }) => { tooltipPlacement="bottom" size="xxl" surface={surface} - aria-label={e2e.components.BackButton.selectors.backArrow} + aria-label={selectors.components.BackButton.backArrow} onClick={onClick} /> ); diff --git a/public/app/core/components/Login/ChangePassword.tsx b/public/app/core/components/Login/ChangePassword.tsx index db29c963032..d1d5acd5589 100644 --- a/public/app/core/components/Login/ChangePassword.tsx +++ b/public/app/core/components/Login/ChangePassword.tsx @@ -1,9 +1,9 @@ import React, { ChangeEvent, PureComponent, SyntheticEvent } from 'react'; import { Tooltip } from '@grafana/ui'; import { AppEvents } from '@grafana/data'; -import { e2e } from '@grafana/e2e'; import appEvents from 'app/core/app_events'; +import { selectors } from '@grafana/e2e-selectors'; interface Props { onSubmit: (pw: string) => void; @@ -117,7 +117,7 @@ export class ChangePassword extends PureComponent { placement="bottom" content="If you skip you will be prompted to change password next time you login." > - + Skip diff --git a/public/app/core/components/Login/LoginForm.tsx b/public/app/core/components/Login/LoginForm.tsx index d5e5750b45c..1ef5cde8ba4 100644 --- a/public/app/core/components/Login/LoginForm.tsx +++ b/public/app/core/components/Login/LoginForm.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent, PureComponent, SyntheticEvent } from 'react'; -import { e2e } from '@grafana/e2e'; +import { selectors } from '@grafana/e2e-selectors'; import { FormModel } from './LoginCtrl'; @@ -74,7 +74,7 @@ export class LoginForm extends PureComponent { className="gf-form-input login-form-input" required placeholder={this.props.loginHint} - aria-label={e2e.pages.Login.selectors.username} + aria-label={selectors.pages.Login.username} onChange={this.onChangeUsername} /> @@ -87,7 +87,7 @@ export class LoginForm extends PureComponent { ng-model="formModel.password" id="inputPassword" placeholder={this.props.passwordHint} - aria-label={e2e.pages.Login.selectors.password} + aria-label={selectors.pages.Login.password} onChange={this.onChangePassword} /> @@ -95,7 +95,7 @@ export class LoginForm extends PureComponent { {!this.props.isLoggingIn ? ( {copiedPanelPlugins.length === 1 && ( diff --git a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx index 1eafc8c7100..cc49b496be9 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx @@ -1,8 +1,8 @@ // Libraries import React, { FunctionComponent } from 'react'; // Components -import { Tooltip, Icon, IconName, IconType, IconSize } from '@grafana/ui'; -import { e2e } from '@grafana/e2e'; +import { Icon, IconName, IconSize, IconType, Tooltip } from '@grafana/ui'; +import { selectors } from '@grafana/e2e-selectors'; interface Props { icon?: IconName; @@ -31,7 +31,7 @@ export const DashNavButton: FunctionComponent = ({